cleanrbx 1.2__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.
cleanrbx-1.2/PKG-INFO ADDED
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: cleanrbx
3
+ Version: 1.2
4
+ Summary: Delete files from a Rekordbox folder not present in exported Rekordbox XML.
5
+ Author-email: Ádám Vincze <edboyww@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/edboyww/python-clean-rb-folder-by-xml
8
+ Project-URL: Repository, https://github.com/edboyww/python-clean-rb-folder-by-xml
9
+ Project-URL: Issues, https://github.com/edboyww/python-clean-rb-folder-by-xml/issues
10
+ Keywords: rekordbox,xml,cleanup
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Sound/Audio
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: prompt-toolkit>=3.0
25
+
26
+ # cleanrbx
27
+
28
+ cleanrbx compares a Rekordbox XML export against a folder on disk and finds files that are no longer referenced by your Rekordbox library.
29
+
30
+ ## Features
31
+
32
+ - Simulation mode by default (safe preview)
33
+ - Optional deletion mode with confirmation
34
+ - Path filtering with `--skip`
35
+ - Detailed output on screen or in a file
36
+ - XML path validation with `--check-xml`
37
+ - Interactive folder selection when `--folder` is not provided
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install cleanrbx
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ Simulate what would be removed:
48
+
49
+ ```bash
50
+ cleanrbx rekordbox.xml
51
+ ```
52
+
53
+ Clean a specific folder:
54
+
55
+ ```bash
56
+ cleanrbx rekordbox.xml --folder "D:\\Music\\Rekordbox" --clean
57
+ ```
58
+
59
+ Skip selected path fragments:
60
+
61
+ ```bash
62
+ cleanrbx rekordbox.xml --skip backups,samples
63
+ ```
64
+
65
+ Check XML paths that do not exist locally:
66
+
67
+ ```bash
68
+ cleanrbx rekordbox.xml --check-xml
69
+ ```
70
+
71
+ Show all options:
72
+
73
+ ```bash
74
+ cleanrbx --help
75
+ ```
76
+
77
+ ## Notes
78
+
79
+ - Streaming URLs in the XML are ignored.
80
+ - `--clean` deletes files, so run without it first to review results.
81
+ - Back up your library before using deletion mode.
@@ -0,0 +1,56 @@
1
+ # cleanrbx
2
+
3
+ cleanrbx compares a Rekordbox XML export against a folder on disk and finds files that are no longer referenced by your Rekordbox library.
4
+
5
+ ## Features
6
+
7
+ - Simulation mode by default (safe preview)
8
+ - Optional deletion mode with confirmation
9
+ - Path filtering with `--skip`
10
+ - Detailed output on screen or in a file
11
+ - XML path validation with `--check-xml`
12
+ - Interactive folder selection when `--folder` is not provided
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install cleanrbx
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ Simulate what would be removed:
23
+
24
+ ```bash
25
+ cleanrbx rekordbox.xml
26
+ ```
27
+
28
+ Clean a specific folder:
29
+
30
+ ```bash
31
+ cleanrbx rekordbox.xml --folder "D:\\Music\\Rekordbox" --clean
32
+ ```
33
+
34
+ Skip selected path fragments:
35
+
36
+ ```bash
37
+ cleanrbx rekordbox.xml --skip backups,samples
38
+ ```
39
+
40
+ Check XML paths that do not exist locally:
41
+
42
+ ```bash
43
+ cleanrbx rekordbox.xml --check-xml
44
+ ```
45
+
46
+ Show all options:
47
+
48
+ ```bash
49
+ cleanrbx --help
50
+ ```
51
+
52
+ ## Notes
53
+
54
+ - Streaming URLs in the XML are ignored.
55
+ - `--clean` deletes files, so run without it first to review results.
56
+ - Back up your library before using deletion mode.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cleanrbx"
7
+ version = "1.2"
8
+ description = "Delete files from a Rekordbox folder not present in exported Rekordbox XML."
9
+ readme = {file ="README.pypi.md", content-type = "text/markdown"}
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "prompt-toolkit>=3.0",
13
+ ]
14
+
15
+ authors = [
16
+ { name = "Ádám Vincze", email = "edboyww@gmail.com" }
17
+ ]
18
+ license = "MIT"
19
+ keywords = ["rekordbox", "xml", "cleanup"]
20
+ classifiers = [
21
+ "Development Status :: 4 - Beta",
22
+ "Intended Audience :: End Users/Desktop",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Topic :: Multimedia :: Sound/Audio",
31
+ "Topic :: Utilities",
32
+ ]
33
+
34
+ [project.scripts]
35
+ cleanrbx = "cleanrbx.cli:main"
36
+
37
+ [tool.setuptools]
38
+ package-dir = {"" = "src"}
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/edboyww/python-clean-rb-folder-by-xml"
45
+ Repository = "https://github.com/edboyww/python-clean-rb-folder-by-xml"
46
+ Issues = "https://github.com/edboyww/python-clean-rb-folder-by-xml/issues"
cleanrbx-1.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "1.2"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,324 @@
1
+ import argparse
2
+ import os
3
+ import xml.etree.ElementTree as ET
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import List
7
+ from urllib.parse import unquote
8
+
9
+ import prompt_toolkit
10
+ import prompt_toolkit.completion
11
+
12
+ from . import __version__
13
+
14
+ SEP = os.path.sep
15
+ AutocompleteDict = dict[str, "AutocompleteDict | None"]
16
+
17
+
18
+ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
19
+ parser = argparse.ArgumentParser(
20
+ prog="cleanrbx",
21
+ description=(
22
+ "Deletes files from your Rekordbox folder which are not in the library "
23
+ "based on an exported Rekordbox XML"
24
+ ),
25
+ add_help=True,
26
+ )
27
+ parser.add_argument(
28
+ "rekordbox_xml",
29
+ help="The file name of the XML file from Rekordbox",
30
+ )
31
+ parser.add_argument(
32
+ "-c", "--clean", action="store_true", help="do the cleaning", default=False
33
+ )
34
+ parser.add_argument(
35
+ "-s",
36
+ "--simulate",
37
+ action="store_true",
38
+ help="simulate the cleaning to see what would be deleted (default behaviour)",
39
+ default=True,
40
+ )
41
+ parser.add_argument(
42
+ "-f",
43
+ "--folder",
44
+ "--f",
45
+ dest="folder",
46
+ type=str,
47
+ action="store",
48
+ default=None,
49
+ help=(
50
+ "the folder to clean. If it is not given, the user will be able to set "
51
+ "the common folder based on the XML"
52
+ ),
53
+ )
54
+ parser.add_argument(
55
+ "--skip",
56
+ type=str,
57
+ action="store",
58
+ default=None,
59
+ help=(
60
+ "skip the cleaning for these strings in the folder paths, divided by ',' "
61
+ "(applies to both the local files and the paths in the XML file)"
62
+ ),
63
+ )
64
+ parser.add_argument(
65
+ "--details",
66
+ action="store_true",
67
+ help="show the detailed results (per file) on console instead of a summary",
68
+ default=False,
69
+ )
70
+ parser.add_argument(
71
+ "--details-file",
72
+ action="store_true",
73
+ help=(
74
+ "write the detailed results to a text file: either clean_results_<datetime>.txt "
75
+ "or simulate_results_<datetime>.txt"
76
+ ),
77
+ default=False,
78
+ )
79
+ parser.add_argument(
80
+ "--check-xml",
81
+ action="store_true",
82
+ help="check if the XML has any URLs which does not exist in the filesystem",
83
+ default=False,
84
+ )
85
+ parser.add_argument(
86
+ "-v", "--version", action="version", version=f"%(prog)s {__version__}"
87
+ )
88
+ return parser.parse_args(argv)
89
+
90
+
91
+ def should_skip_path(input_arg: Path | str, skip_list: List[str] | None) -> bool:
92
+ try:
93
+ input_path = Path(input_arg)
94
+ except Exception:
95
+ return True
96
+
97
+ parent_str = str(input_path.parent).lower()
98
+ if not skip_list:
99
+ return False
100
+
101
+ for skip in skip_list:
102
+ if skip and skip.lower() in parent_str:
103
+ return True
104
+ return False
105
+
106
+
107
+ def get_path_list_from_rekordbox_xml(xml_filename: str) -> List[str]:
108
+ try:
109
+ with open(xml_filename, "r", encoding="utf-8") as rxml:
110
+ xml_tree = ET.parse(rxml)
111
+ except FileNotFoundError:
112
+ print("XML file not found")
113
+ raise SystemExit(1)
114
+ except ET.ParseError:
115
+ print("Invalid XML file")
116
+ raise SystemExit(1)
117
+
118
+ xml_tracks = list(xml_tree.iter("TRACK"))
119
+ if not xml_tracks:
120
+ print("No TRACK elements found in the XML file")
121
+ raise SystemExit(1)
122
+
123
+ xml_paths: List[str] = []
124
+ for track in xml_tracks:
125
+ location = track.get("Location")
126
+ if location:
127
+ raw_path = unquote(location.replace("file://localhost/", ""))
128
+ if (
129
+ raw_path.startswith("tidal:")
130
+ or raw_path.startswith("soundcloud:")
131
+ or raw_path.startswith("itunes:")
132
+ or raw_path.startswith("beatsource:")
133
+ or raw_path.startswith("beatport:")
134
+ ):
135
+ continue
136
+ xml_paths.append(raw_path.replace("/", SEP))
137
+
138
+ return xml_paths
139
+
140
+
141
+ def determine_common_path(paths: List[str]) -> str:
142
+ if not paths:
143
+ return ""
144
+ try:
145
+ return os.path.commonpath(paths)
146
+ except ValueError:
147
+ return ""
148
+
149
+
150
+ def autocomplete_dict(path: Path) -> None | AutocompleteDict:
151
+ iter_dir = [item for item in path.iterdir() if item.is_dir()]
152
+ if len(iter_dir) == 0:
153
+ return None
154
+
155
+ folder_dict: AutocompleteDict = {}
156
+ for item in iter_dir:
157
+ try:
158
+ folder_dict[item.name + SEP] = autocomplete_dict(item)
159
+ except PermissionError:
160
+ folder_dict[item.name + SEP] = None
161
+ return folder_dict
162
+
163
+
164
+ def main(argv: list[str] | None = None) -> int:
165
+ args = parse_args(argv)
166
+
167
+ xml_paths = get_path_list_from_rekordbox_xml(args.rekordbox_xml)
168
+ common_path = ""
169
+
170
+ if args.folder:
171
+ if not os.path.exists(args.folder):
172
+ print("Error: The given path does not exist. Please enter a valid path.\n")
173
+ return 1
174
+ in_xml = False
175
+ for path in xml_paths:
176
+ if args.folder in path:
177
+ common_path = args.folder
178
+ in_xml = True
179
+ break
180
+ if not in_xml:
181
+ print("Error: There are no files for the given path in the XML.\n")
182
+ return 1
183
+
184
+ if common_path == "":
185
+ initial_common_path = determine_common_path(xml_paths)
186
+ try:
187
+ autocomplete_data = autocomplete_dict(Path(initial_common_path))
188
+ except PermissionError:
189
+ print("ERROR: No permission to process common path in XML")
190
+ return 1
191
+ common_path = initial_common_path
192
+ if autocomplete_data is not None:
193
+ print(
194
+ "\nSet the folder you want to clean starting with the common folder for all "
195
+ "the files in the XML file. Press TAB to see the folders on a current level "
196
+ "and SPACE to get to the next level. You can set the path by pressing ENTER.\n"
197
+ )
198
+
199
+ completer = prompt_toolkit.completion.NestedCompleter.from_nested_dict(
200
+ autocomplete_data
201
+ )
202
+
203
+ while True:
204
+ print("SELECT THE PATH:")
205
+ input_raw = prompt_toolkit.prompt(initial_common_path, completer=completer)
206
+ input_path = input_raw.strip().replace(" ", "")
207
+ common_path = initial_common_path + input_path
208
+ if not os.path.exists(common_path):
209
+ print("The path does not exist. Please enter a valid path.\n")
210
+ continue
211
+ print(
212
+ f"\nThe selected path is\n{common_path}\nIS THE PATH OKAY? (Y)es / (N)o:",
213
+ end=" ",
214
+ )
215
+ inp = input().lower()
216
+ if inp not in ("y", "n"):
217
+ print("Please enter 'Y' or 'N' to answer the question:", end=" ")
218
+ elif inp == "y":
219
+ break
220
+
221
+ print(
222
+ f"\nStarting to {'clean' if args.clean else 'simulate cleaning'} from folder {common_path}"
223
+ )
224
+ file_postfix = ""
225
+ if args.details_file:
226
+ file_postfix = datetime.now().strftime("%Y%m%d_%H%M%S")
227
+ print(
228
+ "The details will be written to the following file: "
229
+ f"{'clean' if args.clean else 'simulate'}_details_{file_postfix}.txt"
230
+ )
231
+
232
+ if args.clean:
233
+ print(
234
+ "\nWE ARE ABOUT TO DELETE FILES FROM YOUR REKORDBOX LIBRARY! MAKE SURE YOUR LIBRARY "
235
+ "IS BACKED UP\nDo you want to proceed?\n\n(Y)es / (N)o"
236
+ )
237
+ while True:
238
+ inp = input().lower()
239
+ if inp not in ("y", "n"):
240
+ print("Please enter 'Y' or 'N' to answer the question")
241
+ else:
242
+ break
243
+ if inp == "n":
244
+ print("\nOk, bye!")
245
+ return 0
246
+
247
+ deleted_files = 0
248
+ deleted_details = (
249
+ "\n\nDeleted files:\n--------------\n"
250
+ if args.clean
251
+ else "\n\nFiles to be deleted:\n--------------------\n"
252
+ )
253
+ skipped_files = 0
254
+ skipped_details = "\n\n\nSkipped files:\n--------------\n"
255
+
256
+ skip_list: List[str] | None = None
257
+ if args.skip:
258
+ skip_list = [item.strip() for item in args.skip.split(",") if item.strip()]
259
+
260
+ for path in Path(common_path).rglob("*"):
261
+ if not path.is_file():
262
+ continue
263
+
264
+ resolved = str(path)
265
+ if should_skip_path(resolved, skip_list):
266
+ skipped_files += 1
267
+ skipped_details += f"\nS: {resolved.replace(common_path, '')}"
268
+ continue
269
+
270
+ if resolved not in xml_paths:
271
+ deleted_files += 1
272
+ rel = resolved.replace(common_path, "")
273
+ deleted_details += f"\nD: {rel} (not listed in Rekordbox XML export)"
274
+ if args.clean:
275
+ path.unlink()
276
+
277
+ results = (
278
+ f"\nSUMMARY:\n========\n"
279
+ f"{'Deleted files' if args.clean else 'Files to be deleted'}: {deleted_files}\n"
280
+ f"Skipped files: {skipped_files}\n"
281
+ )
282
+
283
+ xml_paths_not_found: List[str] = []
284
+ xml_paths_not_found_details = ""
285
+ if args.check_xml:
286
+ for xml_path in xml_paths:
287
+ if not os.path.exists(xml_path):
288
+ xml_paths_not_found.append(xml_path)
289
+ if len(xml_paths_not_found) > 0:
290
+ results += f"Paths in XML not found: {len(xml_paths_not_found)}"
291
+ if args.details or args.details_file:
292
+ xml_paths_not_found_details += (
293
+ "\n\n\nPaths in the XML file not found:\n"
294
+ "---------------------------------\n"
295
+ )
296
+ for notfound in xml_paths_not_found:
297
+ xml_paths_not_found_details += f"\nX: {notfound}"
298
+
299
+ print(results)
300
+
301
+ if args.details or args.details_file:
302
+ details = ""
303
+ if deleted_files:
304
+ details += deleted_details
305
+ if skipped_files:
306
+ details += skipped_details
307
+ if args.check_xml and xml_paths_not_found:
308
+ details += xml_paths_not_found_details
309
+ if args.details:
310
+ print(details)
311
+ if args.details_file:
312
+ results += details
313
+ with open(
314
+ f"{'clean' if args.clean else 'simulate'}_details_{file_postfix}.txt",
315
+ "w",
316
+ encoding="utf-8",
317
+ ) as details_file:
318
+ details_file.write(results)
319
+
320
+ return 0
321
+
322
+
323
+ if __name__ == "__main__":
324
+ raise SystemExit(main())
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: cleanrbx
3
+ Version: 1.2
4
+ Summary: Delete files from a Rekordbox folder not present in exported Rekordbox XML.
5
+ Author-email: Ádám Vincze <edboyww@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/edboyww/python-clean-rb-folder-by-xml
8
+ Project-URL: Repository, https://github.com/edboyww/python-clean-rb-folder-by-xml
9
+ Project-URL: Issues, https://github.com/edboyww/python-clean-rb-folder-by-xml/issues
10
+ Keywords: rekordbox,xml,cleanup
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Sound/Audio
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: prompt-toolkit>=3.0
25
+
26
+ # cleanrbx
27
+
28
+ cleanrbx compares a Rekordbox XML export against a folder on disk and finds files that are no longer referenced by your Rekordbox library.
29
+
30
+ ## Features
31
+
32
+ - Simulation mode by default (safe preview)
33
+ - Optional deletion mode with confirmation
34
+ - Path filtering with `--skip`
35
+ - Detailed output on screen or in a file
36
+ - XML path validation with `--check-xml`
37
+ - Interactive folder selection when `--folder` is not provided
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install cleanrbx
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ Simulate what would be removed:
48
+
49
+ ```bash
50
+ cleanrbx rekordbox.xml
51
+ ```
52
+
53
+ Clean a specific folder:
54
+
55
+ ```bash
56
+ cleanrbx rekordbox.xml --folder "D:\\Music\\Rekordbox" --clean
57
+ ```
58
+
59
+ Skip selected path fragments:
60
+
61
+ ```bash
62
+ cleanrbx rekordbox.xml --skip backups,samples
63
+ ```
64
+
65
+ Check XML paths that do not exist locally:
66
+
67
+ ```bash
68
+ cleanrbx rekordbox.xml --check-xml
69
+ ```
70
+
71
+ Show all options:
72
+
73
+ ```bash
74
+ cleanrbx --help
75
+ ```
76
+
77
+ ## Notes
78
+
79
+ - Streaming URLs in the XML are ignored.
80
+ - `--clean` deletes files, so run without it first to review results.
81
+ - Back up your library before using deletion mode.
@@ -0,0 +1,11 @@
1
+ README.pypi.md
2
+ pyproject.toml
3
+ src/cleanrbx/__init__.py
4
+ src/cleanrbx/__main__.py
5
+ src/cleanrbx/cli.py
6
+ src/cleanrbx.egg-info/PKG-INFO
7
+ src/cleanrbx.egg-info/SOURCES.txt
8
+ src/cleanrbx.egg-info/dependency_links.txt
9
+ src/cleanrbx.egg-info/entry_points.txt
10
+ src/cleanrbx.egg-info/requires.txt
11
+ src/cleanrbx.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cleanrbx = cleanrbx.cli:main
@@ -0,0 +1 @@
1
+ prompt-toolkit>=3.0
@@ -0,0 +1 @@
1
+ cleanrbx