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 +81 -0
- cleanrbx-1.2/README.pypi.md +56 -0
- cleanrbx-1.2/pyproject.toml +46 -0
- cleanrbx-1.2/setup.cfg +4 -0
- cleanrbx-1.2/src/cleanrbx/__init__.py +1 -0
- cleanrbx-1.2/src/cleanrbx/__main__.py +4 -0
- cleanrbx-1.2/src/cleanrbx/cli.py +324 -0
- cleanrbx-1.2/src/cleanrbx.egg-info/PKG-INFO +81 -0
- cleanrbx-1.2/src/cleanrbx.egg-info/SOURCES.txt +11 -0
- cleanrbx-1.2/src/cleanrbx.egg-info/dependency_links.txt +1 -0
- cleanrbx-1.2/src/cleanrbx.egg-info/entry_points.txt +2 -0
- cleanrbx-1.2/src/cleanrbx.egg-info/requires.txt +1 -0
- cleanrbx-1.2/src/cleanrbx.egg-info/top_level.txt +1 -0
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 @@
|
|
|
1
|
+
__version__ = "1.2"
|
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prompt-toolkit>=3.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cleanrbx
|