-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathrename.py
156 lines (137 loc) · 4.39 KB
/
rename.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python
# file: rename.py
# vim:fileencoding=utf-8:fdm=marker:ft=python
#
# Copyright © 2015-2018 R.F. Smith <[email protected]>.
# SPDX-License-Identifier: MIT
# Created: 2015-09-06T11:45:52+0200
# Last modified: 2021-12-31T14:56:39+0100
"""
Utility to rename files.
It renames the files given on the command line in the sequence that they are
given to <prefix><number>, keeping the extension of the original file.
"""
import argparse
import logging
import os
import re
import sys
__version__ = "2021.12.31"
def main():
"""
Entry point for rename.py.
"""
args = setup()
if args.numbers:
logging.info("sort files by number")
args.files = sorted_by_number(args.files)
pairs = newnames(args.files, args.prefix, args.start, args.width)
if not args.dryrun:
rename(pairs)
else:
logging.basicConfig(
level=logging.INFO, format="%(levelname)s: %(message)s", force=True
)
logging.info("dry run")
report(pairs)
def setup():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-p",
"--prefix",
default="picture-",
help='prefix for the image name (default "picture-")',
)
parser.add_argument(
"-s", "--start", type=int, default=1, help="first number to use (default 1)"
)
parser.add_argument(
"-w", "--width", type=int, default=2, help="field width for number (default 2)"
)
parser.add_argument(
"-n",
"--numbers",
action="store_true",
help="sort filenames by the last number that is part of the name",
)
parser.add_argument(
"-d",
"--dry-run",
dest="dryrun",
action="store_true",
help="do not actually rename, but report how files would be renamed",
)
parser.add_argument("-v", "--version", action="version", version=__version__)
parser.add_argument(
"--log",
default="warning",
choices=["debug", "info", "warning", "error"],
help="logging level (defaults to 'warning')",
)
parser.add_argument(
"files", metavar="file", nargs="*", help="one or more files to process"
)
args = parser.parse_args(sys.argv[1:])
logging.basicConfig(
level=getattr(logging, args.log.upper(), None),
format="%(levelname)s: %(message)s",
)
return args
def rename(pairs):
for old, new in pairs:
try:
os.rename(old, new)
except OSError as e:
print(f'Could not rename "{old}" to "{new}": {e}')
def report(pairs):
for old, new in pairs:
logging.info(f"“{old}” would be renamed to “{new}”")
def sorted_by_number(paths):
"""Sort paths by the last number occurring in the filename."""
combined = [(re.findall("\d+", p), p) for p in paths]
cleaned = sorted([(int(nl[-1]), p) for nl, p in combined if nl], key=lambda x: x[0])
return [p for n, p in cleaned]
def newnames(paths, prefix, start, precision):
"""
Generate new filenames, keeping the path and extension.
Arguments:
path: Iterable of path names
prefix: New filename prefix to use
start: Initial number to use after the prefix.
precision: How to format the number.
Returns:
List of (path, newpath) tuples
"""
if not prefix:
raise ValueError("empty prefix")
number = int(start)
if number < 0:
raise ValueError("negative numbers not allowed")
precision = int(precision)
if precision < 0:
raise ValueError("precision must be positive")
if isinstance(paths, str):
paths = [paths]
req_prec = len(str(len(paths)))
if req_prec > precision:
logging.warning(f"precision changed from {precision} to {req_prec}")
precision = req_prec
rv = []
for path in paths:
head, tail = os.path.split(path)
logging.debug(f"head = {head}")
logging.debug(f"tail = {tail}")
if not tail:
number += 1
continue
if head and not head.endswith(os.path.sep):
head = head + os.path.sep
_, ext = os.path.splitext(tail)
t = "".join([prefix, r"{:0", str(precision), r"d}", ext])
newpath = "".join([head, t.format(number)])
logging.debug(f"newpath = {newpath}")
number += 1
rv.append((path, newpath))
return rv
if __name__ == "__main__":
main()