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 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()