chname 2.1.3__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.
Potentially problematic release.
This version of chname might be problematic. Click here for more details.
- chname-2.1.3/LICENSE +21 -0
- chname-2.1.3/PKG-INFO +54 -0
- chname-2.1.3/README.md +38 -0
- chname-2.1.3/pyproject.toml +28 -0
- chname-2.1.3/src/chname/__init__.py +0 -0
- chname-2.1.3/src/chname/__main__.py +274 -0
chname-2.1.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021- Steve Scholnick
|
|
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.
|
chname-2.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: chname
|
|
3
|
+
Version: 2.1.3
|
|
4
|
+
Summary: Renames files in powerful ways
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Steve Scholnick
|
|
7
|
+
Author-email: scholnicks@gmail.com
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: docopt-ng (>=0.9.0,<0.10.0)
|
|
13
|
+
Project-URL: Repository, https://github.com/scholnicks/chname
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# chname
|
|
17
|
+
|
|
18
|
+
chname renames files in powerful ways.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Usage:
|
|
22
|
+
chname [options] [<files>...]
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
-a, --append=<suffix> Suffix to be appended
|
|
26
|
+
-d, --delimiter=<delimiter> Specifies the delimiter for fixing numerical filenames
|
|
27
|
+
--directory=<directory> Destination directory [default: .]
|
|
28
|
+
-f, --fix=<maximum number of digits> Fixes numerical file names
|
|
29
|
+
-h, --help Show this help screen
|
|
30
|
+
-l, --lower Translates the filenames to lowercase
|
|
31
|
+
--merge Merges the files in order specfied on command line
|
|
32
|
+
-o, --order Take any input files and renames them in numerical order
|
|
33
|
+
-p, --prepend=<prefix> Prefix to be prepended
|
|
34
|
+
--random Randomizes the files
|
|
35
|
+
-r, --remove=<pattern> Pattern to be removed, can be a regex
|
|
36
|
+
-q, --quiet Quiet mode
|
|
37
|
+
-s, --substitute=<substitution pattern> Substitutes a pattern (old/new, old can be a regex)
|
|
38
|
+
-t, --test Test mode (Just prints the rename operations)
|
|
39
|
+
--titles=<input file with titles> Rename the files by names in the specified input file
|
|
40
|
+
--usage Detailed usage information
|
|
41
|
+
-v, --verbose Verbose mode
|
|
42
|
+
--version Prints the version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install chname
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
chname is freeware released under the [MIT License](https://github.com/scholnicks/chname/blob/main/LICENSE).
|
|
54
|
+
|
chname-2.1.3/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# chname
|
|
2
|
+
|
|
3
|
+
chname renames files in powerful ways.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Usage:
|
|
7
|
+
chname [options] [<files>...]
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
-a, --append=<suffix> Suffix to be appended
|
|
11
|
+
-d, --delimiter=<delimiter> Specifies the delimiter for fixing numerical filenames
|
|
12
|
+
--directory=<directory> Destination directory [default: .]
|
|
13
|
+
-f, --fix=<maximum number of digits> Fixes numerical file names
|
|
14
|
+
-h, --help Show this help screen
|
|
15
|
+
-l, --lower Translates the filenames to lowercase
|
|
16
|
+
--merge Merges the files in order specfied on command line
|
|
17
|
+
-o, --order Take any input files and renames them in numerical order
|
|
18
|
+
-p, --prepend=<prefix> Prefix to be prepended
|
|
19
|
+
--random Randomizes the files
|
|
20
|
+
-r, --remove=<pattern> Pattern to be removed, can be a regex
|
|
21
|
+
-q, --quiet Quiet mode
|
|
22
|
+
-s, --substitute=<substitution pattern> Substitutes a pattern (old/new, old can be a regex)
|
|
23
|
+
-t, --test Test mode (Just prints the rename operations)
|
|
24
|
+
--titles=<input file with titles> Rename the files by names in the specified input file
|
|
25
|
+
--usage Detailed usage information
|
|
26
|
+
-v, --verbose Verbose mode
|
|
27
|
+
--version Prints the version
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install chname
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
chname is freeware released under the [MIT License](https://github.com/scholnicks/chname/blob/main/LICENSE).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "chname"
|
|
3
|
+
version = "2.1.3"
|
|
4
|
+
description = "Renames files in powerful ways"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Steve Scholnick",email = "scholnicks@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license="MIT"
|
|
10
|
+
license-files=["LICENSE"]
|
|
11
|
+
requires-python = ">=3.13"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"docopt-ng (>=0.9.0,<0.10.0)"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
repository="https://github.com/scholnicks/chname"
|
|
18
|
+
|
|
19
|
+
[tool.poetry]
|
|
20
|
+
packages = [{include = "chname", from = "src"}]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
25
|
+
build-backend = "poetry.core.masonry.api"
|
|
26
|
+
|
|
27
|
+
[tool.poetry.scripts]
|
|
28
|
+
chname = "chname.__main__:main"
|
|
File without changes
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env python -B
|
|
2
|
+
# vi: set syntax=python ts=4 sw=4 sts=4 et ff=unix ai si :
|
|
3
|
+
#
|
|
4
|
+
# (c) Steven Scholnick <scholnicks@gmail.com>
|
|
5
|
+
# The chname source code is published under a MIT license.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
chname: Renames files in powerful ways
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
chname [options] [<files>...]
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
-a, --append=<suffix> Suffix to be appended
|
|
15
|
+
-d, --delimiter=<delimiter> Specifies the delimiter for fixing numerical filenames
|
|
16
|
+
--directory=<directory> Destination directory [default: .]
|
|
17
|
+
-f, --fix=<maximum number of digits> Fixes numerical file names
|
|
18
|
+
-h, --help Show this help screen
|
|
19
|
+
-l, --lower Translates the filenames to lowercase
|
|
20
|
+
--merge Merges the files in order specfied on command line
|
|
21
|
+
-o, --order Take any input files and fluxes them in numerical order
|
|
22
|
+
-p, --prepend=<prefix> Prefix to be prepended
|
|
23
|
+
--random Randomizes the files
|
|
24
|
+
-r, --remove=<pattern> Pattern to be removed, can be a regex
|
|
25
|
+
-q, --quiet Quiet mode
|
|
26
|
+
-s, --substitute=<substitution pattern> Substitutes a pattern (old/new, old can be a regex)
|
|
27
|
+
-t, --test Test mode (Just prints the operations)
|
|
28
|
+
--titles=<input file with titles> Rename the files by names in the specified input file
|
|
29
|
+
--usage Detailed usage information
|
|
30
|
+
-v, --verbose Verbose mode
|
|
31
|
+
--version Prints the version
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import os
|
|
35
|
+
import random
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
|
|
39
|
+
from docopt import docopt
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
"""Main Method"""
|
|
44
|
+
global arguments
|
|
45
|
+
arguments = docopt(__doc__, version="chname 2.1.4")
|
|
46
|
+
|
|
47
|
+
if arguments["--test"]:
|
|
48
|
+
arguments["--verbose"] = True
|
|
49
|
+
|
|
50
|
+
flux(arguments["<files>"])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def flux(files):
|
|
54
|
+
"""Renames the specified files"""
|
|
55
|
+
|
|
56
|
+
if arguments["--usage"] or not files:
|
|
57
|
+
usage()
|
|
58
|
+
|
|
59
|
+
if arguments["--verbose"]:
|
|
60
|
+
arguments["--quiet"] = False
|
|
61
|
+
print("Renaming: {}\n".format(", ".join(files)))
|
|
62
|
+
|
|
63
|
+
if arguments["--random"]:
|
|
64
|
+
randomizeFiles(files)
|
|
65
|
+
elif arguments["--merge"]:
|
|
66
|
+
mergeFiles(files)
|
|
67
|
+
elif arguments["--titles"]:
|
|
68
|
+
nameFilesByInputFile(files)
|
|
69
|
+
elif arguments["--order"]:
|
|
70
|
+
orderFiles(files)
|
|
71
|
+
else:
|
|
72
|
+
for fileName in files:
|
|
73
|
+
performRenameOperation(fileName)
|
|
74
|
+
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def nameFilesByInputFile(files):
|
|
79
|
+
"""Names files by using an input text file"""
|
|
80
|
+
extension = calculateExtension(files)
|
|
81
|
+
|
|
82
|
+
with open(arguments["--titles"], "r") as fp:
|
|
83
|
+
exportFileNames = [line.strip() for line in fp if line.strip()]
|
|
84
|
+
|
|
85
|
+
if len(files) != len(exportFileNames):
|
|
86
|
+
raise SystemExit(
|
|
87
|
+
"{} filenames ({}) and files length ({}) do not match".format(
|
|
88
|
+
arguments["--titles"], len(exportFileNames), len(files)
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
filenameTemplate = (
|
|
93
|
+
r"{num:02d} - {filename}{extension}" if len(files) < 100 else r"{num:04d} - {filename}{extension}"
|
|
94
|
+
)
|
|
95
|
+
index = 1
|
|
96
|
+
for currentFilePath, newFileName in zip(files, exportFileNames):
|
|
97
|
+
newFilePath = os.path.join(
|
|
98
|
+
os.path.dirname(currentFilePath),
|
|
99
|
+
filenameTemplate.format(num=index, filename=newFileName, extension=extension),
|
|
100
|
+
)
|
|
101
|
+
rename_file(currentFilePath, newFilePath)
|
|
102
|
+
index += 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def orderFiles(files):
|
|
106
|
+
"""Orders the files"""
|
|
107
|
+
filenameTemplate = r"{num:02d} - {filename}" if len(files) < 100 else r"{num:04d} - {filename}"
|
|
108
|
+
|
|
109
|
+
for index, currentFilePath in enumerate(sorted(files), 1):
|
|
110
|
+
newFilePath = os.path.join(
|
|
111
|
+
os.path.dirname(currentFilePath),
|
|
112
|
+
filenameTemplate.format(num=index, filename=os.path.basename(currentFilePath)),
|
|
113
|
+
)
|
|
114
|
+
rename_file(currentFilePath, newFilePath)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def randomizeFiles(files):
|
|
118
|
+
"""randomly shuffles a list of files with the same extension"""
|
|
119
|
+
|
|
120
|
+
# determine the extension
|
|
121
|
+
extension = calculateExtension(files)
|
|
122
|
+
|
|
123
|
+
# do the shuffle
|
|
124
|
+
random.shuffle(files)
|
|
125
|
+
|
|
126
|
+
prefix = arguments["--prepend"] if arguments["--prepend"] else "file"
|
|
127
|
+
|
|
128
|
+
# rename the files in numeric order
|
|
129
|
+
for index, filename in enumerate(files, 1):
|
|
130
|
+
new_file_name = os.path.join(
|
|
131
|
+
os.path.dirname(filename),
|
|
132
|
+
"{prefix}_{num:04d}{extension}".format(prefix=prefix, num=index, extension=extension),
|
|
133
|
+
)
|
|
134
|
+
rename_file(filename, new_file_name)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def mergeFiles(files):
|
|
138
|
+
"""reorders a set of files in order in a target directory"""
|
|
139
|
+
|
|
140
|
+
if not arguments["--directory"]:
|
|
141
|
+
raise SystemExit("--directory must be set")
|
|
142
|
+
|
|
143
|
+
# determine the extension
|
|
144
|
+
extension = calculateExtension(files)
|
|
145
|
+
|
|
146
|
+
prefix = arguments["--prepend"] if arguments["--prepend"] else "file"
|
|
147
|
+
|
|
148
|
+
# rename the files in argument specified order
|
|
149
|
+
for index, filename in enumerate(files, 1):
|
|
150
|
+
new_file_name = os.path.join(
|
|
151
|
+
arguments["--directory"], "{prefix}_{num:04d}".format(prefix=prefix, num=index) + extension
|
|
152
|
+
)
|
|
153
|
+
rename_file(filename, new_file_name)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def calculateExtension(files):
|
|
157
|
+
"""determines a single extension"""
|
|
158
|
+
extensions = set((os.path.splitext(f)[1].lower() for f in files))
|
|
159
|
+
if len(extensions) > 1:
|
|
160
|
+
raise SystemExit("Only one extension allowed. Found: {}".format(", ".join(extensions)))
|
|
161
|
+
|
|
162
|
+
return extensions.pop()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def performRenameOperation(fileName):
|
|
166
|
+
"""Performs a renaming operation on the specified filename"""
|
|
167
|
+
if not os.path.exists(fileName):
|
|
168
|
+
if not arguments["--quiet"]:
|
|
169
|
+
print("{} does not exist, skipping.".format(fileName), file=sys.stderr)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
newFileName = fileName
|
|
173
|
+
|
|
174
|
+
if arguments["--lower"]:
|
|
175
|
+
newFileName = newFileName.lower()
|
|
176
|
+
|
|
177
|
+
if arguments["--append"]:
|
|
178
|
+
newFileName = newFileName + arguments["--append"]
|
|
179
|
+
|
|
180
|
+
if arguments["--prepend"]:
|
|
181
|
+
newFileName = arguments["--prepend"] + newFileName
|
|
182
|
+
|
|
183
|
+
if arguments["--remove"]:
|
|
184
|
+
newFileName = re.sub(arguments["--remove"], r"", newFileName)
|
|
185
|
+
|
|
186
|
+
if arguments["--delimiter"]:
|
|
187
|
+
if not (arguments["--delimiter"] and arguments["--fix"]):
|
|
188
|
+
raise SystemExit("--delimiter and --fix are both required")
|
|
189
|
+
newFileName = fixNumbers(newFileName, arguments["--delimiter"], arguments["--fix"])
|
|
190
|
+
|
|
191
|
+
if arguments["--substitute"]:
|
|
192
|
+
newFileName = substitute(newFileName, arguments["--substitute"])
|
|
193
|
+
|
|
194
|
+
if newFileName != fileName:
|
|
195
|
+
rename_file(fileName, newFileName)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def rename_file(oldName, newName):
|
|
199
|
+
"""Performs the actual file rename"""
|
|
200
|
+
if arguments["--verbose"]:
|
|
201
|
+
print("Renaming {} to {}".format(oldName, newName))
|
|
202
|
+
|
|
203
|
+
if not arguments["--test"]:
|
|
204
|
+
os.rename(oldName, newName)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def substitute(fileName, pattern):
|
|
208
|
+
"""Performs the pattern substitution"""
|
|
209
|
+
try:
|
|
210
|
+
(old, new) = re.match(r"^(.*)/(.*)$", pattern).groups()
|
|
211
|
+
return re.sub(old, new, fileName)
|
|
212
|
+
except AttributeError:
|
|
213
|
+
raise SystemExit("chname: Illegal substitute pattern. Pattern must be old/new")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def fixNumbers(fileName, delimiter, numberLength):
|
|
217
|
+
"""Fixes the numeric part of a filename"""
|
|
218
|
+
if delimiter not in fileName:
|
|
219
|
+
return fileName
|
|
220
|
+
|
|
221
|
+
(base, extension) = os.path.splitext(fileName)
|
|
222
|
+
(prefix, number) = base.split(delimiter, 2)
|
|
223
|
+
|
|
224
|
+
sequenceValue = number
|
|
225
|
+
|
|
226
|
+
for i in range(len(number), int(numberLength)):
|
|
227
|
+
sequenceValue = "0" + sequenceValue
|
|
228
|
+
|
|
229
|
+
return prefix + delimiter + sequenceValue + extension
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def usage():
|
|
233
|
+
print(
|
|
234
|
+
__doc__
|
|
235
|
+
+ """
|
|
236
|
+
|
|
237
|
+
Merge
|
|
238
|
+
-----
|
|
239
|
+
To merge files from two different directories into the current directory:
|
|
240
|
+
|
|
241
|
+
chname --merge d1/* d2/*
|
|
242
|
+
|
|
243
|
+
Input Files: d1/file1.txt d1/file2.txt d2/file1.txt
|
|
244
|
+
Results: ./file_0001.txt ./file_0002.txt ./file_0003.txt
|
|
245
|
+
|
|
246
|
+
where file_0003.txt is d2/file3.txt
|
|
247
|
+
|
|
248
|
+
All of the files must have the same extension. The default filename format is file_NUMBER.extension. file_ can be changed
|
|
249
|
+
using the --prepend option.
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
Order
|
|
253
|
+
-----
|
|
254
|
+
|
|
255
|
+
Adds a numerical prefix to sorted input files. Example:
|
|
256
|
+
|
|
257
|
+
chname --order filea.mp3 fileb.mp3
|
|
258
|
+
|
|
259
|
+
becomes:
|
|
260
|
+
|
|
261
|
+
01 - filea.mp3 02 - fileb.mp3
|
|
262
|
+
|
|
263
|
+
Random
|
|
264
|
+
------
|
|
265
|
+
|
|
266
|
+
Randomly names files. --prepend can be use to specify the base filename (defaults to file)
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
)
|
|
270
|
+
sys.exit(0)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
main()
|