mp3tools 1.2.4__tar.gz → 1.2.7__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.
- {mp3tools-1.2.4 → mp3tools-1.2.7}/PKG-INFO +6 -22
- mp3tools-1.2.7/README.md +35 -0
- mp3tools-1.2.7/mp3tools/VERSION +1 -0
- mp3tools-1.2.7/mp3tools/cli.py +588 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools.egg-info/PKG-INFO +6 -22
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools.egg-info/SOURCES.txt +2 -1
- mp3tools-1.2.7/tests/test_cli.py +158 -0
- mp3tools-1.2.4/README.md +0 -51
- mp3tools-1.2.4/mp3tools/VERSION +0 -1
- mp3tools-1.2.4/mp3tools/cli.py +0 -392
- {mp3tools-1.2.4 → mp3tools-1.2.7}/Containerfile +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/HISTORY.md +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/LICENSE +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/MANIFEST.in +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools/__init__.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools/__main__.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools/base.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools/mp3tool.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools.egg-info/dependency_links.txt +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools.egg-info/entry_points.txt +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools.egg-info/requires.txt +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/mp3tools.egg-info/top_level.txt +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/setup.cfg +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/setup.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/tests/__init__.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/tests/conftest.py +0 -0
- {mp3tools-1.2.4 → mp3tools-1.2.7}/tests/test_base.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mp3tools
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.7
|
|
4
4
|
Summary: Awesome mp3tools created by carsten-engelke
|
|
5
5
|
Home-page: https://github.com/carsten-engelke/mp3tools/
|
|
6
6
|
Author: carsten-engelke
|
|
@@ -57,26 +57,10 @@ Merge mp3 files and set correct audio length using foobar2000 with an automated
|
|
|
57
57
|
pip install mp3tools
|
|
58
58
|
|
|
59
59
|
## Usage
|
|
60
|
-
Command-line-use:
|
|
61
60
|
```
|
|
62
|
-
mp3tools
|
|
63
|
-
|
|
64
|
-
mp3tools
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
[foobarpath] determines the path to your foobar2000 installation. Please provide in case it differs from 'C:/Program Files (x86)/foobar2000/foobar2000.exe'
|
|
68
|
-
[autowaittime] determines whether to automatically close foobar2000 after some seconds. Use -1 to disable and any number to set the waiting time.
|
|
69
|
-
|
|
70
|
-
mp3tools pack-subdirs [group-size] [dir] [file-filter] [copy-mode]
|
|
71
|
-
[group-size] determines the number of files to put into each directory
|
|
72
|
-
[dir] determines the directory in which to perform the script. Use '.' to select the current directory
|
|
73
|
-
[file-filter] Filter the file list according to this
|
|
74
|
-
[copy-mode] If 'True', the files are copied into the created subfolders. If 'False' they are moved (Use with caution).
|
|
75
|
-
|
|
76
|
-
mp3tools unpack-subdirs [dir] [subdir-filter] [filter] [copy-mode] [remove-dir]")
|
|
77
|
-
[dir] determines the directory in which to perform the script. Use '.' to select the current directory
|
|
78
|
-
[subdir-filter] Filter the subdir list according to this. Use '*' to select any subdirectory
|
|
79
|
-
[filter] Filter the file list according to this
|
|
80
|
-
[copy-mode] If 'True', the files are copied into the parent folder. If 'False' they are moved (Use with caution).
|
|
81
|
-
[remove-dir] If 'True', the subdirectories are deleted. If 'False' they are left as they are.
|
|
61
|
+
Usage: mp3tools merge-mp3 [dir] [subdir-mode] [foobarpath] [autowaittime]
|
|
62
|
+
Usage: mp3tools pack-subdirs [group-size] [dir] [file-filter] [copy-mode]
|
|
63
|
+
Usage: mp3tools unpack-subdirs [dir] [subdir-filter] [file-filter] [copy-mode] [removesubdir-mode]
|
|
64
|
+
Usage: mp3tools wait=seconds
|
|
65
|
+
Note: This script depends on a foobar2000 installation! (Get it from: https://www.foobar2000.org/)
|
|
82
66
|
```
|
mp3tools-1.2.7/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# mp3tools
|
|
2
|
+
Merge mp3 files and set correct audio length using foobar2000 with an automated python script. These scripts can merge all files in one directory or create one file for each subdirectory.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
- mp3tools merge-mp3: merging script, works with foobar2000
|
|
7
|
+
- mp3tools pack-subdirs: pack files into grouped subdirectories (good for large audiobooks)
|
|
8
|
+
- mp3tools unpack-subdirs.py: unpack files from grouped subdirectories (undo mp3tools pack-subdirs)
|
|
9
|
+
|
|
10
|
+
## Version
|
|
11
|
+
1.2.2 Fixed
|
|
12
|
+
|
|
13
|
+
1.2.0 Added PI/CI Support thanks to https://github.com/rochacbruno/python-project-template
|
|
14
|
+
|
|
15
|
+
1.0.0 release version. Ported to vscode.
|
|
16
|
+
|
|
17
|
+
0.2.0 bug corrected foobar needs to be called from working directory as the command line plugin cannot handle empty spaces in file names or paths given by command line
|
|
18
|
+
|
|
19
|
+
0.1.0 initial release. Port from windows script to python, introducing automation
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
- Python (script was created using python 3.7.0) (https://www.python.org/)
|
|
23
|
+
- foobar2000 (https://www.foobar2000.org/)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
pip install mp3tools
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
```
|
|
30
|
+
Usage: mp3tools merge-mp3 [dir] [subdir-mode] [foobarpath] [autowaittime]
|
|
31
|
+
Usage: mp3tools pack-subdirs [group-size] [dir] [file-filter] [copy-mode]
|
|
32
|
+
Usage: mp3tools unpack-subdirs [dir] [subdir-filter] [file-filter] [copy-mode] [removesubdir-mode]
|
|
33
|
+
Usage: mp3tools wait=seconds
|
|
34
|
+
Note: This script depends on a foobar2000 installation! (Get it from: https://www.foobar2000.org/)
|
|
35
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.7
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
# # Merge mp3 files from the current directory or from subfolders (each into one mp3 file)
|
|
2
|
+
# Group files into subfolders. Create groups of a certain size.
|
|
3
|
+
# Ungroup files from subfolders.
|
|
4
|
+
# Carsten Engelke 2023
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
from _thread import start_new_thread
|
|
13
|
+
from pynput.keyboard import Controller, Key # type: ignore
|
|
14
|
+
import math as m
|
|
15
|
+
from shutil import copyfile
|
|
16
|
+
|
|
17
|
+
__all__ = ["main", "mergemp3", "packsubdirs", "unpackdirs"]
|
|
18
|
+
|
|
19
|
+
VERSIONSTR = "1.0.8 (C)opyright Carsten Engelke 2022"
|
|
20
|
+
foobarpath = "C:/Users/carst/scoop/shims/foobar2000.exe"
|
|
21
|
+
mp3tagpath = "C:/Users/carst/scoop/shims/mp3tag.exe"
|
|
22
|
+
workingdir = os.getcwd()
|
|
23
|
+
autowaittime = 5
|
|
24
|
+
groupsize = 15
|
|
25
|
+
filefilter = ".mp3"
|
|
26
|
+
subdirfilter = "subdir-"
|
|
27
|
+
copymode = False
|
|
28
|
+
removesubdirmode = True
|
|
29
|
+
yes = ["yes", "y", "ja", "j", "", "1"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def mergesubdirs(dir, foobarpath, autowaittime):
|
|
33
|
+
filelist = []
|
|
34
|
+
with os.scandir(dir) as it:
|
|
35
|
+
for subdir in it:
|
|
36
|
+
if not subdir.name.startswith(".") and subdir.is_dir():
|
|
37
|
+
aimfile = mergedir(subdir, True)
|
|
38
|
+
if aimfile != None:
|
|
39
|
+
filelist.append(aimfile)
|
|
40
|
+
callfoobar(foobarpath, dir, filelist, autowaittime)
|
|
41
|
+
print("Merging subdirectories of " + dir + " done.")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def mergedir(dir, copytoparent):
|
|
45
|
+
if copytoparent:
|
|
46
|
+
filestr = os.path.dirname(dir.path) + os.sep + dir.name + ".mp3"
|
|
47
|
+
else:
|
|
48
|
+
filestr = (
|
|
49
|
+
os.path.dirname(dir) + os.sep + os.path.basename(dir) + ".mp3"
|
|
50
|
+
)
|
|
51
|
+
mp3number = " ".join(os.listdir(dir)).count(".mp3")
|
|
52
|
+
mp3now = 0
|
|
53
|
+
if mp3number > 0:
|
|
54
|
+
print(
|
|
55
|
+
"mp3 file found. Merging to: " + filestr + "...0%\r",
|
|
56
|
+
end=" ",
|
|
57
|
+
flush=True,
|
|
58
|
+
)
|
|
59
|
+
if os.path.exists(filestr):
|
|
60
|
+
os.remove(filestr)
|
|
61
|
+
with open(filestr, "ab") as aimfile:
|
|
62
|
+
with os.scandir(dir) as it:
|
|
63
|
+
for f in it:
|
|
64
|
+
if f.name.endswith(".mp3") and f.is_file():
|
|
65
|
+
mp3now += 1
|
|
66
|
+
srcfile = open(f, "rb")
|
|
67
|
+
aimfile.write(srcfile.read())
|
|
68
|
+
return aimfile
|
|
69
|
+
else:
|
|
70
|
+
print("No mp3 in '" + dir.path + "' found.")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def callfoobar(foobarpath, workdir, filelist, autowaittime):
|
|
75
|
+
# if not in windows skip foobar2000 execution
|
|
76
|
+
if os.name == "nt":
|
|
77
|
+
filenamelist = []
|
|
78
|
+
for f in filelist:
|
|
79
|
+
filenamelist.append(f.name)
|
|
80
|
+
rebuildcmdlist = [foobarpath]
|
|
81
|
+
rebuildcmdlist.append("/runcmd-files=Util/Rebuild")
|
|
82
|
+
rebuildcmdlist.extend(filenamelist)
|
|
83
|
+
fixcmdlist = [foobarpath]
|
|
84
|
+
fixcmdlist.append("/runcmd-files=Util/Fix")
|
|
85
|
+
fixcmdlist.extend(filenamelist)
|
|
86
|
+
minimizecmdlist = [foobarpath]
|
|
87
|
+
minimizecmdlist.append(
|
|
88
|
+
"/runcmd-files=Utilities/Optimize file layout + minimize file size"
|
|
89
|
+
)
|
|
90
|
+
minimizecmdlist.extend(filenamelist)
|
|
91
|
+
os.chdir(workdir)
|
|
92
|
+
if autowaittime >= 0:
|
|
93
|
+
print(
|
|
94
|
+
"Calling foobar2000 for rebuilding the mp3 stream. Automatically ending in:"
|
|
95
|
+
+ str(autowaittime * len(filenamelist)),
|
|
96
|
+
end="...",
|
|
97
|
+
flush=True,
|
|
98
|
+
)
|
|
99
|
+
start_new_thread(
|
|
100
|
+
closefoobar, (foobarpath, autowaittime, len(filelist), True)
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
print(
|
|
104
|
+
"Calling foobar2000 for rebuilding the mp3 stream. Please close the foobar window to continue",
|
|
105
|
+
end="...",
|
|
106
|
+
flush=True,
|
|
107
|
+
)
|
|
108
|
+
subprocess.call(rebuildcmdlist)
|
|
109
|
+
print("done")
|
|
110
|
+
if autowaittime >= 0:
|
|
111
|
+
print(
|
|
112
|
+
"Calling foobar2000 for fixing the mp3 metadata length. Automatically ending in:"
|
|
113
|
+
+ str(autowaittime * len(filenamelist) / 2),
|
|
114
|
+
end="...",
|
|
115
|
+
flush=True,
|
|
116
|
+
)
|
|
117
|
+
start_new_thread(
|
|
118
|
+
closefoobar,
|
|
119
|
+
(foobarpath, autowaittime / 2, len(filelist), True),
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
print(
|
|
123
|
+
"Calling foobar2000 for fixing the mp3 metadata length. Please close the foobar window to continue",
|
|
124
|
+
end="...",
|
|
125
|
+
flush=True,
|
|
126
|
+
)
|
|
127
|
+
subprocess.call(fixcmdlist)
|
|
128
|
+
print("done")
|
|
129
|
+
if autowaittime >= 0:
|
|
130
|
+
print(
|
|
131
|
+
"Calling foobar2000 for minimizing file. Automatically ending in "
|
|
132
|
+
+ str(autowaittime * len(filenamelist) / 3),
|
|
133
|
+
end="...",
|
|
134
|
+
flush=True,
|
|
135
|
+
)
|
|
136
|
+
start_new_thread(
|
|
137
|
+
closefoobar,
|
|
138
|
+
(foobarpath, autowaittime / 3, len(filelist), False),
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
print(
|
|
142
|
+
"Calling foobar2000 for minimizing file. Please close the foobar window to continue",
|
|
143
|
+
end="...",
|
|
144
|
+
flush=True,
|
|
145
|
+
)
|
|
146
|
+
subprocess.call(minimizecmdlist)
|
|
147
|
+
print("done")
|
|
148
|
+
return True
|
|
149
|
+
else:
|
|
150
|
+
print(
|
|
151
|
+
"foobar2000 onyl available in windows - skipping. Use mp3diags manually in linux e.g."
|
|
152
|
+
)
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def closefoobar(foobarpath, sleeptime, filenumber, press):
|
|
157
|
+
closecmdlist = [foobarpath, "/exit"]
|
|
158
|
+
k = Controller()
|
|
159
|
+
if press:
|
|
160
|
+
time.sleep(sleeptime)
|
|
161
|
+
k.press(Key.enter)
|
|
162
|
+
k.release(Key.enter)
|
|
163
|
+
time.sleep(sleeptime * filenumber)
|
|
164
|
+
subprocess.call(closecmdlist)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def mergemp3(dir, mergesub, foobarpath, autowaittime):
|
|
168
|
+
|
|
169
|
+
print(
|
|
170
|
+
"calling mergemp3 [mergesub="
|
|
171
|
+
+ str(mergesub)
|
|
172
|
+
+ "] [dir="
|
|
173
|
+
+ dir
|
|
174
|
+
+ "] [foobarpath="
|
|
175
|
+
+ foobarpath
|
|
176
|
+
+ "] [autowaittime="
|
|
177
|
+
+ str(autowaittime)
|
|
178
|
+
+ "]"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if mergesub:
|
|
182
|
+
mergesubdirs(dir, foobarpath, autowaittime)
|
|
183
|
+
else:
|
|
184
|
+
aimfile = mergedir(dir, False)
|
|
185
|
+
if aimfile != None:
|
|
186
|
+
callfoobar(foobarpath, dir, [aimfile], autowaittime)
|
|
187
|
+
print("Merging mp3s in " + dir + " done.")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def packsubdirs(groupsize, dir, filter, copy):
|
|
191
|
+
|
|
192
|
+
l = []
|
|
193
|
+
with os.scandir(dir) as it:
|
|
194
|
+
for entry in it:
|
|
195
|
+
if entry.name.find(filter) >= 0 and os.path.isfile(entry):
|
|
196
|
+
l.append(entry)
|
|
197
|
+
dirnum = m.ceil(len(l) / groupsize)
|
|
198
|
+
anz = 0
|
|
199
|
+
dirnumactual = 1
|
|
200
|
+
dirname = dir + "/subdir-" + str(dirnumactual)
|
|
201
|
+
try:
|
|
202
|
+
os.makedirs(dirname)
|
|
203
|
+
except:
|
|
204
|
+
print("directory already exists")
|
|
205
|
+
for entry in l:
|
|
206
|
+
if anz >= groupsize:
|
|
207
|
+
anz = 0
|
|
208
|
+
dirnumactual += 1
|
|
209
|
+
dirname = dir + "/subdir-" + str(dirnumactual)
|
|
210
|
+
print("create: " + dirname)
|
|
211
|
+
try:
|
|
212
|
+
os.mkdir(dirname)
|
|
213
|
+
except:
|
|
214
|
+
print("directory already exists")
|
|
215
|
+
if copy:
|
|
216
|
+
copyfile(dir + "/" + entry.name, dirname + "/" + entry.name)
|
|
217
|
+
print("FILE_COPIED: " + entry.name)
|
|
218
|
+
else:
|
|
219
|
+
os.rename(dir + "/" + entry.name, dirname + "/" + entry.name)
|
|
220
|
+
print("FILE_MOVED: " + entry.name)
|
|
221
|
+
anz += 1
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def unpackdirs(workdir, subdirfilter, filefilter, copy, remove):
|
|
225
|
+
l = []
|
|
226
|
+
with os.scandir(workdir) as it:
|
|
227
|
+
for entry in it:
|
|
228
|
+
if subdirfilter == "*" and os.path.isdir(entry):
|
|
229
|
+
l.append(entry)
|
|
230
|
+
else:
|
|
231
|
+
if entry.name.find(subdirfilter) >= 0 and os.path.isdir(entry):
|
|
232
|
+
l.append(entry)
|
|
233
|
+
|
|
234
|
+
for subdir in l:
|
|
235
|
+
with os.scandir(subdir) as it:
|
|
236
|
+
for entry in it:
|
|
237
|
+
if entry.name.find(filefilter) >= 0 and os.path.isfile(entry):
|
|
238
|
+
if copy:
|
|
239
|
+
copyfile(
|
|
240
|
+
os.path.join(workdir, subdir.name, entry.name),
|
|
241
|
+
os.path.join(workdir, entry.name),
|
|
242
|
+
)
|
|
243
|
+
print("FILE_COPIED: " + entry.name)
|
|
244
|
+
else:
|
|
245
|
+
os.rename(
|
|
246
|
+
os.path.join(workdir, subdir.name, entry.name),
|
|
247
|
+
os.path.join(workdir, entry.name),
|
|
248
|
+
)
|
|
249
|
+
print("FILE_MOVED: " + entry.name)
|
|
250
|
+
if remove:
|
|
251
|
+
os.removedirs(subdir)
|
|
252
|
+
print("DIR_REMOVED: " + subdir.name)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def selectsubprogram():
|
|
256
|
+
while True:
|
|
257
|
+
choice = input(
|
|
258
|
+
"(M)erge mp3 files, (P)ack subfolders, (U)npack subfolders or (A)bort?"
|
|
259
|
+
)
|
|
260
|
+
if choice == "M" or choice == "m":
|
|
261
|
+
run_command(build_parser().parse_args(["merge-mp3"]))
|
|
262
|
+
return
|
|
263
|
+
if choice == "P" or choice == "p":
|
|
264
|
+
run_command(build_parser().parse_args(["pack-subdirs"]))
|
|
265
|
+
return
|
|
266
|
+
if choice == "U" or choice == "u":
|
|
267
|
+
run_command(build_parser().parse_args(["unpack-subdirs"]))
|
|
268
|
+
return
|
|
269
|
+
if choice == "A" or choice == "a":
|
|
270
|
+
sys.exit(0)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def parse_boolean(value):
|
|
274
|
+
normalized = value.lower()
|
|
275
|
+
if normalized in ("true", "yes", "y", "ja", "j", "1", "on"):
|
|
276
|
+
return True
|
|
277
|
+
if normalized in ("false", "no", "n", "nein", "0", "off", "move"):
|
|
278
|
+
return False
|
|
279
|
+
raise argparse.ArgumentTypeError(
|
|
280
|
+
"expected true/false, yes/no, y/n, ja/nein, 1/0, on/off, or move"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def positive_integer(value):
|
|
285
|
+
try:
|
|
286
|
+
parsed = int(value)
|
|
287
|
+
except ValueError:
|
|
288
|
+
raise argparse.ArgumentTypeError("must be an integer")
|
|
289
|
+
if parsed < 1:
|
|
290
|
+
raise argparse.ArgumentTypeError("must be greater than zero")
|
|
291
|
+
return parsed
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def build_parser():
|
|
295
|
+
parser = argparse.ArgumentParser(
|
|
296
|
+
prog="mp3tools",
|
|
297
|
+
description=(
|
|
298
|
+
"Merge MP3 files and organize files into subdirectories.\n"
|
|
299
|
+
"Run without a command to start the interactive wizard."
|
|
300
|
+
),
|
|
301
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
302
|
+
epilog=(
|
|
303
|
+
"For argument details and examples, run one of:\n"
|
|
304
|
+
" mp3tools merge-mp3 --help\n"
|
|
305
|
+
" mp3tools pack-subdirs --help\n"
|
|
306
|
+
" mp3tools unpack-subdirs --help\n"
|
|
307
|
+
" mp3tools wait --help\n"
|
|
308
|
+
"\n"
|
|
309
|
+
"The executable path options may be used before or after a command.\n"
|
|
310
|
+
"Run without a command to use the interactive wizard."
|
|
311
|
+
"\n"
|
|
312
|
+
"Merging and fixing MP3 metadata requires foobar2000 on Windows."
|
|
313
|
+
),
|
|
314
|
+
)
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
"-V", "--version", action="version", version=VERSIONSTR
|
|
317
|
+
)
|
|
318
|
+
add_executable_arguments(parser)
|
|
319
|
+
commands = parser.add_subparsers(dest="command")
|
|
320
|
+
|
|
321
|
+
merge = commands.add_parser("merge-mp3", help="merge MP3 files")
|
|
322
|
+
add_executable_arguments(merge, suppress_defaults=True)
|
|
323
|
+
merge.add_argument(
|
|
324
|
+
"dir",
|
|
325
|
+
nargs="?",
|
|
326
|
+
metavar="DIR",
|
|
327
|
+
help="directory containing MP3s (default: current directory; '.' also selects it)",
|
|
328
|
+
)
|
|
329
|
+
merge.add_argument(
|
|
330
|
+
"subdir_mode",
|
|
331
|
+
nargs="?",
|
|
332
|
+
metavar="SUBDIR-MODE",
|
|
333
|
+
type=parse_boolean,
|
|
334
|
+
help=(
|
|
335
|
+
"merge each subdirectory into a separate MP3; accepts "
|
|
336
|
+
"true/false, yes/no, y/n, ja/nein, 1/0, on/off (omitted: ask)"
|
|
337
|
+
),
|
|
338
|
+
)
|
|
339
|
+
merge.add_argument(
|
|
340
|
+
"legacy_foobar_path",
|
|
341
|
+
nargs="?",
|
|
342
|
+
metavar="FOOBAR-PATH",
|
|
343
|
+
help="legacy positional foobar2000 executable path; prefer --foobar-path",
|
|
344
|
+
)
|
|
345
|
+
merge.add_argument(
|
|
346
|
+
"autowaittime",
|
|
347
|
+
nargs="?",
|
|
348
|
+
metavar="SECONDS",
|
|
349
|
+
type=int,
|
|
350
|
+
help="seconds before auto-closing foobar2000; use -1 to disable (default: 5)",
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
pack = commands.add_parser(
|
|
354
|
+
"pack-subdirs", help="group files into subdirectories"
|
|
355
|
+
)
|
|
356
|
+
add_executable_arguments(pack, suppress_defaults=True)
|
|
357
|
+
pack.add_argument(
|
|
358
|
+
"group_size",
|
|
359
|
+
nargs="?",
|
|
360
|
+
metavar="GROUP-SIZE",
|
|
361
|
+
type=positive_integer,
|
|
362
|
+
help="maximum files per subdirectory (default: 15; must be greater than 0)",
|
|
363
|
+
)
|
|
364
|
+
pack.add_argument(
|
|
365
|
+
"dir",
|
|
366
|
+
nargs="?",
|
|
367
|
+
metavar="DIR",
|
|
368
|
+
help="directory containing files (default: current directory; '.' also selects it)",
|
|
369
|
+
)
|
|
370
|
+
pack.add_argument(
|
|
371
|
+
"file_filter",
|
|
372
|
+
nargs="?",
|
|
373
|
+
metavar="FILE-FILTER",
|
|
374
|
+
help="case-sensitive substring selecting files (default: '.mp3')",
|
|
375
|
+
)
|
|
376
|
+
pack.add_argument(
|
|
377
|
+
"copy_mode",
|
|
378
|
+
nargs="?",
|
|
379
|
+
metavar="COPY-MODE",
|
|
380
|
+
type=parse_boolean,
|
|
381
|
+
help=(
|
|
382
|
+
"copy instead of move; accepts true/false, yes/no, y/n, "
|
|
383
|
+
"ja/nein, 1/0, on/off, or 'move' for false "
|
|
384
|
+
"(default: false, move files)"
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
unpack = commands.add_parser(
|
|
389
|
+
"unpack-subdirs", help="move files out of subdirectories"
|
|
390
|
+
)
|
|
391
|
+
add_executable_arguments(unpack, suppress_defaults=True)
|
|
392
|
+
unpack.add_argument(
|
|
393
|
+
"dir",
|
|
394
|
+
nargs="?",
|
|
395
|
+
metavar="DIR",
|
|
396
|
+
help="directory containing subdirectories (default: current directory; '.' also selects it)",
|
|
397
|
+
)
|
|
398
|
+
unpack.add_argument(
|
|
399
|
+
"subdir_filter",
|
|
400
|
+
nargs="?",
|
|
401
|
+
metavar="SUBDIR-FILTER",
|
|
402
|
+
help="case-sensitive substring selecting subdirectories; '*' selects all (default: 'subdir-')",
|
|
403
|
+
)
|
|
404
|
+
unpack.add_argument(
|
|
405
|
+
"file_filter",
|
|
406
|
+
nargs="?",
|
|
407
|
+
metavar="FILE-FILTER",
|
|
408
|
+
help="case-sensitive substring selecting files (default: '.mp3')",
|
|
409
|
+
)
|
|
410
|
+
unpack.add_argument(
|
|
411
|
+
"copy_mode",
|
|
412
|
+
nargs="?",
|
|
413
|
+
metavar="COPY-MODE",
|
|
414
|
+
type=parse_boolean,
|
|
415
|
+
help=(
|
|
416
|
+
"copy instead of move; accepts true/false, yes/no, y/n, "
|
|
417
|
+
"ja/nein, 1/0, on/off, or 'move' for false "
|
|
418
|
+
"(default: false, move files)"
|
|
419
|
+
),
|
|
420
|
+
)
|
|
421
|
+
unpack.add_argument(
|
|
422
|
+
"remove_subdirs",
|
|
423
|
+
nargs="?",
|
|
424
|
+
metavar="REMOVE-SUBDIRS",
|
|
425
|
+
type=parse_boolean,
|
|
426
|
+
help=(
|
|
427
|
+
"remove emptied subdirectories; accepts true/false, yes/no, "
|
|
428
|
+
"y/n, ja/nein, 1/0, on/off "
|
|
429
|
+
"(default: true; ignored if files remain)"
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
wait = commands.add_parser(
|
|
434
|
+
"wait", help="set the foobar2000 wait time, then choose a tool"
|
|
435
|
+
)
|
|
436
|
+
add_executable_arguments(wait, suppress_defaults=True)
|
|
437
|
+
wait.add_argument(
|
|
438
|
+
"seconds",
|
|
439
|
+
nargs="?",
|
|
440
|
+
metavar="SECONDS",
|
|
441
|
+
type=int,
|
|
442
|
+
help="seconds before auto-closing foobar2000; use -1 to disable (default: 5)",
|
|
443
|
+
)
|
|
444
|
+
return parser
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def add_executable_arguments(parser, suppress_defaults=False):
|
|
448
|
+
default = argparse.SUPPRESS if suppress_defaults else foobarpath
|
|
449
|
+
parser.add_argument(
|
|
450
|
+
"--foobar-path",
|
|
451
|
+
metavar="PATH",
|
|
452
|
+
default=default,
|
|
453
|
+
help="path to the foobar2000 executable (default: configured path)",
|
|
454
|
+
)
|
|
455
|
+
default = argparse.SUPPRESS if suppress_defaults else mp3tagpath
|
|
456
|
+
parser.add_argument(
|
|
457
|
+
"--mp3tag-path",
|
|
458
|
+
metavar="PATH",
|
|
459
|
+
default=default,
|
|
460
|
+
help="path to the Mp3tag executable (default: configured path)",
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def confirm(message):
|
|
465
|
+
print(message)
|
|
466
|
+
return input().lower() in yes
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def run_command(args):
|
|
470
|
+
global autowaittime
|
|
471
|
+
global foobarpath
|
|
472
|
+
global mp3tagpath
|
|
473
|
+
|
|
474
|
+
foobarpath = args.foobar_path
|
|
475
|
+
mp3tagpath = args.mp3tag_path
|
|
476
|
+
if args.command == "merge-mp3":
|
|
477
|
+
directory = workingdir if args.dir in (None, ".") else args.dir
|
|
478
|
+
if directory != workingdir and not os.path.isdir(directory):
|
|
479
|
+
choice = input(
|
|
480
|
+
"Specified directory not found: {0} ... will use current "
|
|
481
|
+
"directory instead: {1}...OK? (Y)es or (N)o?".format(
|
|
482
|
+
directory, workingdir
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
if choice.lower() in ("n", "no", "nein"):
|
|
486
|
+
return
|
|
487
|
+
directory = workingdir
|
|
488
|
+
mode = args.subdir_mode
|
|
489
|
+
if mode is None:
|
|
490
|
+
while True:
|
|
491
|
+
choice = input(
|
|
492
|
+
" > Merge (S)ubfolders or this (D)irectory or (A)bort?"
|
|
493
|
+
).lower()
|
|
494
|
+
if choice in ("s", "d"):
|
|
495
|
+
mode = choice == "s"
|
|
496
|
+
break
|
|
497
|
+
if choice == "a":
|
|
498
|
+
return
|
|
499
|
+
path = args.legacy_foobar_path
|
|
500
|
+
if path is None or path == ".":
|
|
501
|
+
path = foobarpath
|
|
502
|
+
wait_time = (
|
|
503
|
+
args.autowaittime
|
|
504
|
+
if args.autowaittime is not None
|
|
505
|
+
else autowaittime
|
|
506
|
+
)
|
|
507
|
+
print("mp3-tools merge-mp3 {0}".format(VERSIONSTR))
|
|
508
|
+
mergemp3(directory, mode, path, wait_time)
|
|
509
|
+
elif args.command == "pack-subdirs":
|
|
510
|
+
group_size = (
|
|
511
|
+
args.group_size if args.group_size is not None else groupsize
|
|
512
|
+
)
|
|
513
|
+
directory = workingdir if args.dir in (None, ".") else args.dir
|
|
514
|
+
filter_value = (
|
|
515
|
+
args.file_filter if args.file_filter is not None else filefilter
|
|
516
|
+
)
|
|
517
|
+
copy = args.copy_mode if args.copy_mode is not None else copymode
|
|
518
|
+
print("pack-subdirs {0}".format(VERSIONSTR))
|
|
519
|
+
print(
|
|
520
|
+
"calling pack-subdirs: [group-size={0}] [dir={1}] "
|
|
521
|
+
"[filter={2}] [copy-mode={3}]".format(
|
|
522
|
+
group_size, directory, filter_value, copy
|
|
523
|
+
)
|
|
524
|
+
)
|
|
525
|
+
if args.group_size is None and not confirm(
|
|
526
|
+
"Pack files according to above settings into subdirectories [y/n]?"
|
|
527
|
+
):
|
|
528
|
+
return
|
|
529
|
+
packsubdirs(group_size, directory, filter_value, copy)
|
|
530
|
+
elif args.command == "unpack-subdirs":
|
|
531
|
+
directory = workingdir if args.dir in (None, ".") else args.dir
|
|
532
|
+
subdirs = (
|
|
533
|
+
args.subdir_filter
|
|
534
|
+
if args.subdir_filter is not None
|
|
535
|
+
else subdirfilter
|
|
536
|
+
)
|
|
537
|
+
filter_value = (
|
|
538
|
+
args.file_filter if args.file_filter is not None else filefilter
|
|
539
|
+
)
|
|
540
|
+
copy = args.copy_mode if args.copy_mode is not None else copymode
|
|
541
|
+
remove = (
|
|
542
|
+
args.remove_subdirs
|
|
543
|
+
if args.remove_subdirs is not None
|
|
544
|
+
else removesubdirmode
|
|
545
|
+
)
|
|
546
|
+
print("unpack-subdirs {0}".format(VERSIONSTR))
|
|
547
|
+
print(
|
|
548
|
+
"calling unpack-subdirs [dir={0}] [subdir-filter={1}] "
|
|
549
|
+
"[file-filter={2}] [copy-mode={3}] [removesubdir-mode={4}]".format(
|
|
550
|
+
directory, subdirs, filter_value, copy, remove
|
|
551
|
+
)
|
|
552
|
+
)
|
|
553
|
+
if args.dir is None and not confirm(
|
|
554
|
+
"Unpack files according to above settings into subdirectories [y/n]?"
|
|
555
|
+
):
|
|
556
|
+
return
|
|
557
|
+
unpackdirs(directory, subdirs, filter_value, copy, remove)
|
|
558
|
+
elif args.command == "wait":
|
|
559
|
+
if args.seconds is not None:
|
|
560
|
+
autowaittime = args.seconds
|
|
561
|
+
selectsubprogram()
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def main(argv=None):
|
|
565
|
+
"""
|
|
566
|
+
The main function executes on commands:
|
|
567
|
+
`python -m mp3tools` and `$ mp3tools `.
|
|
568
|
+
|
|
569
|
+
This is your program's entry point.
|
|
570
|
+
"""
|
|
571
|
+
if argv is None:
|
|
572
|
+
argv = sys.argv[1:]
|
|
573
|
+
if not argv:
|
|
574
|
+
selectsubprogram()
|
|
575
|
+
return
|
|
576
|
+
|
|
577
|
+
if len(argv) == 1 and argv[0].startswith("wait="):
|
|
578
|
+
argv = ["wait", argv[0][5:]]
|
|
579
|
+
parser = build_parser()
|
|
580
|
+
args = parser.parse_args(argv)
|
|
581
|
+
if args.command is None:
|
|
582
|
+
parser.print_help()
|
|
583
|
+
else:
|
|
584
|
+
run_command(args)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
if __name__ == "__main__":
|
|
588
|
+
main()
|