Auto-DLP 2025.1.69__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.
Files changed (33) hide show
  1. auto_dlp-2025.1.69/.gitignore +5 -0
  2. auto_dlp-2025.1.69/LICENSE.txt +21 -0
  3. auto_dlp-2025.1.69/PKG-INFO +162 -0
  4. auto_dlp-2025.1.69/README.md +143 -0
  5. auto_dlp-2025.1.69/auto_dlp/YoutubeDataAPIv3.py +238 -0
  6. auto_dlp-2025.1.69/auto_dlp/__init__.py +106 -0
  7. auto_dlp-2025.1.69/auto_dlp/__main__.py +4 -0
  8. auto_dlp-2025.1.69/auto_dlp/adb.py +90 -0
  9. auto_dlp-2025.1.69/auto_dlp/adb_fs.py +38 -0
  10. auto_dlp-2025.1.69/auto_dlp/adb_fs_cleaning.py +86 -0
  11. auto_dlp-2025.1.69/auto_dlp/age_hints.py +40 -0
  12. auto_dlp-2025.1.69/auto_dlp/channels.py +27 -0
  13. auto_dlp-2025.1.69/auto_dlp/config_intepreter.py +275 -0
  14. auto_dlp-2025.1.69/auto_dlp/download_manager.py +57 -0
  15. auto_dlp-2025.1.69/auto_dlp/downloader.py +95 -0
  16. auto_dlp-2025.1.69/auto_dlp/example_file.py +51 -0
  17. auto_dlp-2025.1.69/auto_dlp/ffmpeg.py +53 -0
  18. auto_dlp-2025.1.69/auto_dlp/file_cleaning.py +41 -0
  19. auto_dlp-2025.1.69/auto_dlp/file_locations.py +109 -0
  20. auto_dlp-2025.1.69/auto_dlp/fs_sync.py +10 -0
  21. auto_dlp-2025.1.69/auto_dlp/metadata.py +82 -0
  22. auto_dlp-2025.1.69/auto_dlp/name_cleaning.py +45 -0
  23. auto_dlp-2025.1.69/auto_dlp/playlist_items.py +62 -0
  24. auto_dlp-2025.1.69/auto_dlp/terminal_formatting.py +2 -0
  25. auto_dlp-2025.1.69/auto_dlp/thumbnails.py +56 -0
  26. auto_dlp-2025.1.69/auto_dlp/unavailable_items.py +22 -0
  27. auto_dlp-2025.1.69/auto_dlp/utils.py +46 -0
  28. auto_dlp-2025.1.69/auto_dlp/variables.py +1 -0
  29. auto_dlp-2025.1.69/auto_dlp/version.py +1 -0
  30. auto_dlp-2025.1.69/icon.png +0 -0
  31. auto_dlp-2025.1.69/makefile +23 -0
  32. auto_dlp-2025.1.69/pyproject.toml +30 -0
  33. auto_dlp-2025.1.69/version_script.py +75 -0
@@ -0,0 +1,5 @@
1
+ */__pycache__
2
+ .gitignore
3
+ .idea/
4
+ dist/
5
+ *.sh
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 J. Günthner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: Auto-DLP
3
+ Version: 2025.1.69
4
+ Summary: A simple and lightweight system for downloading and managing youtube playlists and songs (based on yt-dlp)
5
+ Author-email: Vorak <5m9mnf37q@mozmail.com>
6
+ License-File: LICENSE.txt
7
+ Keywords: Youtube,dlp,music
8
+ Classifier: Environment :: Console
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: Microsoft
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.8
14
+ Requires-Dist: mutagen>=1.40.0
15
+ Requires-Dist: pycopy>=2025.1.14
16
+ Requires-Dist: requests>=2.20.0
17
+ Requires-Dist: yt-dlp>=2024.12.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Auto-DLP
21
+ A simple command-line utility for automating the process of downloading songs using yt-dlp and sorting them into folders. Can also push files to android devices.
22
+
23
+ The philosophy of this tool is to download songs and lay them out beautifully in the filesystem, this means that there are a lot of features to get the names of files, folders, etc. correct.
24
+
25
+ ## Getting Started
26
+ Use ```pipx install auto-dlp``` to install the command directly or use
27
+ ```pip install auto-dlp``` to install the module and run it using ```<Your python installation> -m auto_dlp```.
28
+ ```Your python installation``` is usually one of ```python```, ```python3``` or ```py```.
29
+
30
+ In the command line try ```auto-dlp -h```. If this runs without an error, you have successfully installed the tool.
31
+
32
+ Now choose a directory in which you would like your music to be stored, there run the command ```auto-dlp --create-example .```. This will create an example ```auto-dlp.json``` file which will download some songs and playlists. For the more advanced features of this tool, read the following section.
33
+
34
+ ## Advanced Features
35
+
36
+ ### Pushing the files to folders (Syncing)
37
+ ```jsonc
38
+ {
39
+ //
40
+ "sync dirs": ["the/directory/you/would like to sync to"]
41
+
42
+ // alternatively, if you have a lot of directories you might
43
+ // want to sync to:
44
+ // "sync": [
45
+ // "path1",
46
+ // "path2",
47
+ // ...
48
+ // ]
49
+ // The syncing is not invoked if the destination does not exist
50
+
51
+ // Optional entries
52
+
53
+ // Additional folders that should be copied to your device
54
+ // By default Auto-DLP only copies the artist's folders
55
+ // "extra sync folders": [...]
56
+ }
57
+ ```
58
+
59
+
60
+ ### Pushing the files to Android devices
61
+ Use the normal method described in the previous header if possible.
62
+
63
+ First you must configure the feature in your `auto-dlp.json` file:
64
+ ```jsonc
65
+ {
66
+ // Where various files are cached
67
+ "config dir": "~/.local/state/youtube-downloader/",
68
+ // This is where your artists go
69
+ "artists": {
70
+ ...
71
+ },
72
+ // This is the important line
73
+ // adb stands for Android-Debug-Bridge and
74
+ // is an offial tool for debugging (and copying files)
75
+ "adb push": true
76
+
77
+ // The following options are optional
78
+
79
+ // This option can be used to restart the adb service every
80
+ // time the program is executed, use this if it is making problems
81
+ // "restart adb": true
82
+
83
+ // Where the files should be copied to
84
+ // "adb push dir": "/sdcard/Music"
85
+
86
+ // Additional folders that should be copied to your device
87
+ // By default Auto-DLP only copies the artist's folders
88
+ // "extra sync folders": [...]
89
+ }
90
+ ```
91
+
92
+ Then you must plug your phone into your Laptop with the phone's debug mode enabled. Now just start `auto-dlp` normally and it should copy the files over automatically.
93
+
94
+ ### Heeelp!! I get errors on certain playlists/songs
95
+
96
+ For this there is two configuration options:
97
+ ```jsonc
98
+ {
99
+ // Where various files are cached
100
+ "config dir": "~/.local/state/youtube-downloader/",
101
+ // This is where your artists go
102
+ "artists": {
103
+ ...
104
+ },
105
+ // With this entry you can forcefully rename songs that
106
+ // are automatically downloaded as part of a playlist.
107
+ // This is for example a fix for the problem of empty names.
108
+ "rename": {
109
+ "<some name>": "<a valid name>",
110
+ ...
111
+ },
112
+ // This key is for playlists that just don't want to be downloaded
113
+ // and make the program throw the following error:
114
+ // "The Youtube Api returned the same result twice"
115
+ // Ironically this is (so far) only on official Youtube Playlists
116
+ "fragile playlists": [
117
+ "RDCLAK5uy_lp8LtelM9GiSwRFGGQjctKaGoHcrgQVEU",
118
+ ...
119
+ ]
120
+ }
121
+ ```
122
+
123
+ ### The names don't look like I want them to
124
+
125
+ The names for songs in playlists are simply those entered
126
+ on Youtube. These are, however, sometimes shit.
127
+
128
+ For this there is the following configuration options:
129
+ ```jsonc
130
+ {
131
+ // Where various files are cached
132
+ "config dir": "~/.local/state/youtube-downloader/",
133
+ // This is where your artists go
134
+ "artists": {
135
+ ...
136
+ },
137
+ // This entry specifies this that are searched for in names
138
+ // and removed. Case is ignored and the values are treated as
139
+ // python regexes
140
+ "name rules": [
141
+ // This value will remove all text from titles that contain a
142
+ // '(official' followed by anything and then another ')'
143
+ // i.e '(Official Music Video)'
144
+ "\\(official.*\\)",
145
+ ...
146
+ ],
147
+ // This entry is somewhat meta. It is intented to make writing
148
+ // regexes for "name rules" easier. It is a simple find and replace
149
+ // system.
150
+ // In the following example the string "<" is replaced by something
151
+ // that matches '[' or '('
152
+ // The strings are replaced in the "name rules"
153
+ // For example we could write the above rule as
154
+ // "<official.*>"
155
+ // Now it would also match (and remove) '[Official Video]'
156
+ "rule macros": {
157
+ "<": "[\\[\\(]",
158
+ ">": "[\\]\\)]",
159
+ ...
160
+ }
161
+ }
162
+ ```
@@ -0,0 +1,143 @@
1
+ # Auto-DLP
2
+ A simple command-line utility for automating the process of downloading songs using yt-dlp and sorting them into folders. Can also push files to android devices.
3
+
4
+ The philosophy of this tool is to download songs and lay them out beautifully in the filesystem, this means that there are a lot of features to get the names of files, folders, etc. correct.
5
+
6
+ ## Getting Started
7
+ Use ```pipx install auto-dlp``` to install the command directly or use
8
+ ```pip install auto-dlp``` to install the module and run it using ```<Your python installation> -m auto_dlp```.
9
+ ```Your python installation``` is usually one of ```python```, ```python3``` or ```py```.
10
+
11
+ In the command line try ```auto-dlp -h```. If this runs without an error, you have successfully installed the tool.
12
+
13
+ Now choose a directory in which you would like your music to be stored, there run the command ```auto-dlp --create-example .```. This will create an example ```auto-dlp.json``` file which will download some songs and playlists. For the more advanced features of this tool, read the following section.
14
+
15
+ ## Advanced Features
16
+
17
+ ### Pushing the files to folders (Syncing)
18
+ ```jsonc
19
+ {
20
+ //
21
+ "sync dirs": ["the/directory/you/would like to sync to"]
22
+
23
+ // alternatively, if you have a lot of directories you might
24
+ // want to sync to:
25
+ // "sync": [
26
+ // "path1",
27
+ // "path2",
28
+ // ...
29
+ // ]
30
+ // The syncing is not invoked if the destination does not exist
31
+
32
+ // Optional entries
33
+
34
+ // Additional folders that should be copied to your device
35
+ // By default Auto-DLP only copies the artist's folders
36
+ // "extra sync folders": [...]
37
+ }
38
+ ```
39
+
40
+
41
+ ### Pushing the files to Android devices
42
+ Use the normal method described in the previous header if possible.
43
+
44
+ First you must configure the feature in your `auto-dlp.json` file:
45
+ ```jsonc
46
+ {
47
+ // Where various files are cached
48
+ "config dir": "~/.local/state/youtube-downloader/",
49
+ // This is where your artists go
50
+ "artists": {
51
+ ...
52
+ },
53
+ // This is the important line
54
+ // adb stands for Android-Debug-Bridge and
55
+ // is an offial tool for debugging (and copying files)
56
+ "adb push": true
57
+
58
+ // The following options are optional
59
+
60
+ // This option can be used to restart the adb service every
61
+ // time the program is executed, use this if it is making problems
62
+ // "restart adb": true
63
+
64
+ // Where the files should be copied to
65
+ // "adb push dir": "/sdcard/Music"
66
+
67
+ // Additional folders that should be copied to your device
68
+ // By default Auto-DLP only copies the artist's folders
69
+ // "extra sync folders": [...]
70
+ }
71
+ ```
72
+
73
+ Then you must plug your phone into your Laptop with the phone's debug mode enabled. Now just start `auto-dlp` normally and it should copy the files over automatically.
74
+
75
+ ### Heeelp!! I get errors on certain playlists/songs
76
+
77
+ For this there is two configuration options:
78
+ ```jsonc
79
+ {
80
+ // Where various files are cached
81
+ "config dir": "~/.local/state/youtube-downloader/",
82
+ // This is where your artists go
83
+ "artists": {
84
+ ...
85
+ },
86
+ // With this entry you can forcefully rename songs that
87
+ // are automatically downloaded as part of a playlist.
88
+ // This is for example a fix for the problem of empty names.
89
+ "rename": {
90
+ "<some name>": "<a valid name>",
91
+ ...
92
+ },
93
+ // This key is for playlists that just don't want to be downloaded
94
+ // and make the program throw the following error:
95
+ // "The Youtube Api returned the same result twice"
96
+ // Ironically this is (so far) only on official Youtube Playlists
97
+ "fragile playlists": [
98
+ "RDCLAK5uy_lp8LtelM9GiSwRFGGQjctKaGoHcrgQVEU",
99
+ ...
100
+ ]
101
+ }
102
+ ```
103
+
104
+ ### The names don't look like I want them to
105
+
106
+ The names for songs in playlists are simply those entered
107
+ on Youtube. These are, however, sometimes shit.
108
+
109
+ For this there is the following configuration options:
110
+ ```jsonc
111
+ {
112
+ // Where various files are cached
113
+ "config dir": "~/.local/state/youtube-downloader/",
114
+ // This is where your artists go
115
+ "artists": {
116
+ ...
117
+ },
118
+ // This entry specifies this that are searched for in names
119
+ // and removed. Case is ignored and the values are treated as
120
+ // python regexes
121
+ "name rules": [
122
+ // This value will remove all text from titles that contain a
123
+ // '(official' followed by anything and then another ')'
124
+ // i.e '(Official Music Video)'
125
+ "\\(official.*\\)",
126
+ ...
127
+ ],
128
+ // This entry is somewhat meta. It is intented to make writing
129
+ // regexes for "name rules" easier. It is a simple find and replace
130
+ // system.
131
+ // In the following example the string "<" is replaced by something
132
+ // that matches '[' or '('
133
+ // The strings are replaced in the "name rules"
134
+ // For example we could write the above rule as
135
+ // "<official.*>"
136
+ // Now it would also match (and remove) '[Official Video]'
137
+ "rule macros": {
138
+ "<": "[\\[\\(]",
139
+ ">": "[\\]\\)]",
140
+ ...
141
+ }
142
+ }
143
+ ```
@@ -0,0 +1,238 @@
1
+ import os.path as path
2
+ import re
3
+ import sys
4
+ from collections import Counter
5
+ from json import dumps
6
+
7
+ from requests import get
8
+
9
+ import auto_dlp.file_locations as fs
10
+ import auto_dlp.utils as utils
11
+
12
+ # def _ask_for_api_key():
13
+ # key = input("Please enter a valid Google Cloud Youtube API key (This is for getting the playlist item (in the future there might be a different implementation for this feature)): ").strip()
14
+ # if key == "":
15
+ # return _ask_for_api_key()
16
+ #
17
+ # fs.touch_file(fs.yt_api_key_file())
18
+ # with fs.yt_api_key_file().open("w") as file:
19
+ # print(key.strip(), file=file)
20
+ #
21
+ # return key
22
+
23
+ # Wow, such secure
24
+ _key_url = "https://pastebin.com/8vmQFLbm"
25
+
26
+
27
+ def _download_api_key():
28
+ response = get(_key_url)
29
+ if response.status_code != 200:
30
+ raise RuntimeError("Could not reach server to request api key, please try again later")
31
+
32
+ pat = re.compile("this is where the key is:(?P<api_key>.*)not any further", flags=re.IGNORECASE)
33
+ match = pat.search(response.text)
34
+ key = match.groupdict()["api_key"]
35
+
36
+ file = fs.yt_api_key_file()
37
+ fs.touch_file(file)
38
+ with open(file, "w") as fhandle:
39
+ fhandle.write(key)
40
+
41
+ return key
42
+
43
+
44
+ @utils.lazy
45
+ def API_KEY():
46
+ file = fs.yt_api_key_file()
47
+ if not path.exists(file) or path.getsize(file) <= 0:
48
+ print("Api key file was not found", file=sys.stderr, flush=True)
49
+ return utils.scramble(_download_api_key()).strip()
50
+
51
+ with open(file) as fhandle:
52
+ return utils.scramble(fhandle.read()).strip()
53
+
54
+
55
+ def request(url, params):
56
+ params["key"] = API_KEY()
57
+ response = get(url, params)
58
+
59
+ if response.status_code == 200 and "error" not in response.json():
60
+ return response
61
+
62
+ try:
63
+ err_msg = response.json()["error"]["message"]
64
+ except KeyError:
65
+ err_msg = "Error message could not be read"
66
+
67
+ if response.status_code == 403 or "API key not valid" in err_msg:
68
+ print("API key was not recognised by Youtube")
69
+ sys.exit()
70
+
71
+ raise RuntimeError(f"Network error: {response.status_code} {err_msg}")
72
+
73
+
74
+ # This method is unaware of name cleaning
75
+ def deduplicate_playlist_items(playlist_items):
76
+ name_counter = Counter()
77
+
78
+ for item in playlist_items:
79
+ name_counter[item["name"]] += 1
80
+
81
+ index_names = set()
82
+
83
+ for name, count in name_counter.items():
84
+ if count > 1:
85
+ index_names.add(name)
86
+
87
+ if len(index_names) == 0:
88
+ return playlist_items
89
+
90
+ name_counter = Counter()
91
+
92
+ for item in playlist_items:
93
+ name = item["name"]
94
+ if item["name"] in index_names:
95
+ name_counter[name] += 1
96
+ item["name"] = f"{name} ={name_counter[name]}="
97
+
98
+ return playlist_items
99
+
100
+
101
+ # currently unused
102
+ # def error_on_name_conflict(config, names, playlist_id):
103
+ # clean_names = defaultdict(lambda: [])
104
+ #
105
+ # for name in names:
106
+ # clean_names[clean_name(config, name)].append(name)
107
+ #
108
+ # conflict_found = False
109
+ #
110
+ # for clean, sources in clean_names.items():
111
+ # if len(sources) > 1:
112
+ # conflict_found = True
113
+ # print(f"The specified name rules caused a name conflict: All of\n {"\n ".join(sources)}\n became {clean}")
114
+ #
115
+ # if conflict_found:
116
+ # print(f"There are name conflicts in playlist {playlist_id}", file=sys.stderr)
117
+ # sys.exit(-1)
118
+
119
+ def get_playlist_items(config, playlist_id):
120
+ def send_request(params):
121
+ params["playlist_id"] = playlist_id
122
+ params["part"] = "contentDetails, snippet"
123
+ params["maxResults"] = 50
124
+ response = request("https://www.googleapis.com/youtube/v3/playlistItems", params)
125
+ items = {}
126
+
127
+ try:
128
+ items_json = response.json()["items"]
129
+
130
+ for item in items_json:
131
+ pos = item["snippet"]["position"]
132
+ items[pos] = {
133
+ "name": item["snippet"]["title"],
134
+ "id": item["contentDetails"]["videoId"]
135
+ }
136
+ except KeyError:
137
+ print(dumps(response.json(), indent=4), file=sys.stderr)
138
+ raise RuntimeError("Failed getting playlist items")
139
+
140
+ if "nextPageToken" in response.json():
141
+ return response.json()["pageInfo"]["totalResults"], response.json()["nextPageToken"], items
142
+ return response.json()["pageInfo"]["totalResults"], None, items
143
+
144
+ received_items = 0
145
+ result_count, page_token, items_map = send_request({})
146
+
147
+ items_list = [None] * result_count
148
+
149
+ def place_items():
150
+ for index, item in items_map.items():
151
+ if items_list[index] is None:
152
+ items_list[index] = item
153
+ continue
154
+
155
+ raise RuntimeError("The Youtube Api returned the same result twice")
156
+
157
+ place_items()
158
+ received_items += 50
159
+
160
+ while received_items < result_count:
161
+ _, page_token, items_map = send_request({"pageToken": page_token})
162
+ try:
163
+ place_items()
164
+ except RuntimeError as e:
165
+ if playlist_id in config.fragile_playlists:
166
+ return deduplicate_playlist_items(list(filter(lambda x: x is not None, items_list)))
167
+ raise e
168
+ received_items += 50
169
+
170
+ result = deduplicate_playlist_items(items_list)
171
+
172
+ # error_on_name_conflict(config, [item["name"] for item in result], playlist_id)
173
+
174
+ return result
175
+
176
+
177
+ def get_song_channel(song_id):
178
+ response = request("https://www.googleapis.com/youtube/v3/videos", {
179
+ "key": API_KEY(),
180
+ "part": "snippet",
181
+ "id": song_id
182
+ })
183
+
184
+ try:
185
+ channel = response.json()["items"][0]["snippet"]["channelId"]
186
+ except KeyError:
187
+ print(dumps(response.json(), indent=4), file=sys.stderr)
188
+ raise RuntimeError("Failed getting song thumbnails")
189
+
190
+ return channel
191
+
192
+
193
+ def get_song_thumbnails(song_id):
194
+ response = request("https://www.googleapis.com/youtube/v3/videos", {
195
+ "key": API_KEY(),
196
+ "part": "snippet",
197
+ "id": song_id
198
+ })
199
+
200
+ try:
201
+ thumbnails = response.json()["items"][0]["snippet"]["thumbnails"]
202
+ except KeyError:
203
+ print(dumps(response.json(), indent=4), file=sys.stderr)
204
+ raise RuntimeError("Failed getting song thumbnails")
205
+
206
+ return thumbnails
207
+
208
+
209
+ def get_playlist_thumbnails(playlist_id):
210
+ response = request("https://www.googleapis.com/youtube/v3/playlists", {
211
+ "key": API_KEY(),
212
+ "part": "snippet",
213
+ "id": playlist_id
214
+ })
215
+
216
+ try:
217
+ thumbnails = response.json()["items"][0]["snippet"]["thumbnails"]
218
+ except KeyError:
219
+ print(dumps(response.json(), indent=4), file=sys.stderr)
220
+ raise RuntimeError("Failed getting song thumbnails")
221
+
222
+ return thumbnails
223
+
224
+
225
+ def get_channel_thumbnails(channel_id):
226
+ response = request("https://www.googleapis.com/youtube/v3/channels", {
227
+ "key": API_KEY(),
228
+ "part": "snippet",
229
+ "id": channel_id
230
+ })
231
+
232
+ try:
233
+ thumbnails = response.json()["items"][0]["snippet"]["thumbnails"]
234
+ except KeyError:
235
+ print(dumps(response.json(), indent=4), file=sys.stderr)
236
+ raise RuntimeError("Failed getting song thumbnails")
237
+
238
+ return thumbnails
@@ -0,0 +1,106 @@
1
+ import argparse
2
+ import json
3
+ import os
4
+ import re
5
+ import sys
6
+ from pathlib import Path
7
+ from traceback import print_exception
8
+
9
+ from auto_dlp import config_intepreter, example_file
10
+ import auto_dlp.version
11
+
12
+ _maybe_wait_after_finishing_execution = True
13
+ _definitely_wait_after_finishing_execution = False
14
+ CONFIG_FILENAME = "auto-dlp.json"
15
+
16
+
17
+ def manage_music(path: Path, redownload_songs=(), test_names=(), playlist_test_names=None, verbose=False):
18
+ original_cur_dir = Path(os.curdir).resolve()
19
+ os.chdir(path)
20
+
21
+ try:
22
+ config_file = path / CONFIG_FILENAME
23
+ if not config_file.exists():
24
+ print(f"No {CONFIG_FILENAME} file found in directory {path.resolve()}", file=sys.stderr)
25
+ print(f"Use auto-dlp DIRECTORY --create-example to create an example {CONFIG_FILENAME} file")
26
+ return
27
+
28
+ with open(config_file) as file:
29
+ string = re.sub("//.*", "", file.read())
30
+ json_data = json.loads(string)
31
+
32
+ config_intepreter.execute(json_data, redownload_songs=redownload_songs, test_names=test_names,
33
+ playlist_test_names=playlist_test_names, verbose=verbose)
34
+ except KeyboardInterrupt:
35
+ print("Program interrupted by user", file=sys.stderr)
36
+
37
+ os.chdir(original_cur_dir)
38
+
39
+
40
+ def command_entry_point():
41
+ try:
42
+ main()
43
+ except Exception as e:
44
+ print_exception(e)
45
+ if _maybe_wait_after_finishing_execution:
46
+ wait_to_close()
47
+ except SystemExit:
48
+ if _definitely_wait_after_finishing_execution:
49
+ wait_to_close()
50
+
51
+ class CustomHelpFormatter(argparse.HelpFormatter):
52
+ def __init__(self, *args, **kwargs):
53
+ super().__init__(*args, **kwargs, max_help_position=70, width=100)
54
+
55
+ def main():
56
+ parser = argparse.ArgumentParser(prog="auto-dlp",
57
+ description="A light-weight program for managing (and downloading) music",
58
+ allow_abbrev=True, add_help=True, exit_on_error=True, formatter_class=CustomHelpFormatter)
59
+ parser.add_argument("FOLDER",
60
+ help=f"The folder in which to execute this program. It must contain an {CONFIG_FILENAME} file.")
61
+ parser.add_argument("-rd", "--redownload", metavar="SONG",
62
+ help="Will delete the current version of the specified song, meaning that it will be redownloaded on the next execution of this program")
63
+ parser.add_argument("-tn", "--test-name", metavar="NAME",
64
+ help="Allows to try out what the renaming system does to a specific string")
65
+ parser.add_argument("-ptn", "--playlist-test-names", metavar="PLAYLIST",
66
+ help="Runs all the names of songs in PLAYLIST against -tn")
67
+ parser.add_argument("-w", "--wait", action="store_true", help="After finishing execution, wait for any input")
68
+ parser.add_argument("-v", "--verbose", action="store_true", help="Makes the script print more information")
69
+ parser.add_argument("--create-example", action="store_true", help=f"Creates a sample {CONFIG_FILENAME} file that will perform elementary functions")
70
+ parser.add_argument("--version", action="store_true", help="Show the version number and exit")
71
+
72
+ args = parser.parse_args()
73
+ global _maybe_wait_after_finishing_execution, _definitely_wait_after_finishing_execution
74
+ _maybe_wait_after_finishing_execution = args.wait
75
+ _definitely_wait_after_finishing_execution = args.wait
76
+
77
+ if args.version:
78
+ print(f"Auto-DLP version {version.program_version}")
79
+ if args.wait:
80
+ wait_to_close()
81
+ return
82
+
83
+ exec_dir = Path(args.FOLDER).expanduser().resolve()
84
+ print(f"Executing Auto-DLP in directory {exec_dir}")
85
+
86
+ if args.create_example:
87
+ example_file.generate_file(exec_dir, CONFIG_FILENAME)
88
+ if args.wait:
89
+ wait_to_close()
90
+ return
91
+
92
+ redownload = () if args.redownload is None else [args.redownload]
93
+ test_names = () if args.test_name is None else [args.test_name]
94
+ playlist = args.playlist_test_names
95
+
96
+ manage_music(exec_dir, redownload_songs=redownload, test_names=test_names, playlist_test_names=playlist,
97
+ verbose=args.verbose)
98
+
99
+ if args.wait:
100
+ wait_to_close()
101
+
102
+ def wait_to_close():
103
+ try:
104
+ input("Press ENTER to close program")
105
+ except KeyboardInterrupt:
106
+ pass
@@ -0,0 +1,4 @@
1
+ from auto_dlp import command_entry_point
2
+
3
+ if __name__ == "__main__":
4
+ command_entry_point()