|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
1 |
|
"""Rename file name. |
|
3
|
|
|
""" |
|
4
|
1 |
|
import os |
|
5
|
1 |
|
import os.path |
|
6
|
1 |
|
import re |
|
7
|
|
|
|
|
8
|
1 |
|
from .core import Common |
|
9
|
1 |
|
from .exceptions import ( |
|
10
|
|
|
InvalidNumberParameterTypeError, |
|
11
|
|
|
ChangeFileNameOSError, |
|
12
|
|
|
InvalidImageParameterTypeError, |
|
13
|
|
|
) |
|
14
|
1 |
|
from .utils.logging import get_logger |
|
15
|
|
|
|
|
16
|
1 |
|
_logger = get_logger("change_filename") |
|
17
|
|
|
|
|
18
|
|
|
|
|
19
|
1 |
|
class ChangeFilename(Common): |
|
20
|
|
|
"""Change file name to only digit name. |
|
21
|
|
|
""" |
|
22
|
|
|
|
|
23
|
1 |
|
def __init__(self, directory_path, digits, extension): |
|
24
|
|
|
"""Constructor |
|
25
|
|
|
|
|
26
|
|
|
Args: |
|
27
|
|
|
directory_path (str): Target directory path. |
|
28
|
|
|
digits (str): Regex target digit. |
|
29
|
|
|
extension (str): Target file extension. |
|
30
|
|
|
""" |
|
31
|
1 |
|
super().__init__() |
|
32
|
1 |
|
self.__directory_path = directory_path |
|
33
|
1 |
|
self.__digits = digits |
|
34
|
1 |
|
self.__exist_files = [] |
|
35
|
1 |
|
self.__extension = self._convert_extension_with_dot(extension) |
|
36
|
1 |
|
self.__max_digit = self._check_digit_format(self.__digits) |
|
37
|
1 |
|
self.__regex_ext = re.compile(self.__extension) |
|
38
|
1 |
|
os.chdir(self.__directory_path) |
|
39
|
|
|
|
|
40
|
1 |
|
@staticmethod |
|
41
|
|
|
def _create_new_name(num, digit, extension): |
|
42
|
|
|
"""Create only digit file name. |
|
43
|
|
|
|
|
44
|
|
|
Args: |
|
45
|
|
|
num (Match): Match object |
|
46
|
|
|
digit (int): digit number |
|
47
|
|
|
extension (str): File Extension like ".jpg" |
|
48
|
|
|
|
|
49
|
|
|
Returns: |
|
50
|
|
|
str: Only digit file name. |
|
51
|
|
|
|
|
52
|
|
|
Raises: |
|
53
|
|
|
InvalidNumberParameterTypeError: If input digit is not Match object. |
|
54
|
|
|
|
|
55
|
|
|
""" |
|
56
|
1 |
|
try: |
|
57
|
1 |
|
return num.group().zfill(digit) + extension |
|
58
|
1 |
|
except AttributeError as attribute_error: |
|
59
|
1 |
|
_logger.exception(attribute_error) |
|
60
|
1 |
|
raise InvalidNumberParameterTypeError() from attribute_error |
|
61
|
|
|
|
|
62
|
1 |
|
def __check_exist_file(self, new_name, old_name, append_list): |
|
63
|
|
|
"""Check current directory and exists same name file, return true. |
|
64
|
|
|
Args: |
|
65
|
|
|
new_name (str): Target file name. |
|
66
|
|
|
old_name (str): before name |
|
67
|
|
|
append_list (bool): If true, make duplicate name list |
|
68
|
|
|
|
|
69
|
|
|
Returns: |
|
70
|
|
|
bool: If success, return true |
|
71
|
|
|
|
|
72
|
|
|
Raises: |
|
73
|
|
|
ChangeFileNameOSError: If input a bad filename or path. |
|
74
|
|
|
""" |
|
75
|
1 |
|
try: |
|
76
|
1 |
|
if os.path.isfile(new_name): |
|
77
|
1 |
|
_logger.info("File Exist: {filename}".format(filename=new_name)) |
|
78
|
1 |
|
if old_name != new_name and append_list: |
|
79
|
1 |
|
_logger.debug( |
|
80
|
|
|
"Append exist files list: {filename}".format(filename=old_name) |
|
81
|
|
|
) |
|
82
|
1 |
|
self.__exist_files.append(old_name) |
|
83
|
1 |
|
return True |
|
84
|
|
|
else: |
|
85
|
1 |
|
return False |
|
86
|
1 |
|
except OSError as os_error: |
|
87
|
1 |
|
_logger.exception(os_error) |
|
88
|
1 |
|
raise ChangeFileNameOSError() from os_error |
|
89
|
|
|
|
|
90
|
1 |
|
def __input_new_file_name(self, old_name, overwrite): |
|
91
|
|
|
"""Provide input command prompt. |
|
92
|
|
|
Args: |
|
93
|
|
|
old_name (str): Old name |
|
94
|
|
|
overwrite (bool): If true, ignore exist file name. |
|
95
|
|
|
|
|
96
|
|
|
Returns: |
|
97
|
|
|
str: new file name. |
|
98
|
|
|
""" |
|
99
|
1 |
|
new_name = input("Input new name? {old_name} =>".format(old_name=old_name)) |
|
100
|
1 |
|
while self.__check_exist_file(new_name, new_name, False) and not overwrite: |
|
101
|
1 |
|
_logger.warn("Already file exist: {new_name}") |
|
102
|
1 |
|
new_name = input( |
|
103
|
|
|
"Input Another file name {old_name} => ?".format(old_name=old_name) |
|
104
|
|
|
) |
|
105
|
1 |
|
return new_name |
|
106
|
|
|
|
|
107
|
1 |
|
def filename_to_digit_number(self): |
|
108
|
|
|
"""Change file name to only digit name. |
|
109
|
|
|
|
|
110
|
|
|
Change file name contains digit like "foo001bar.txt" to |
|
111
|
|
|
only digit file name like "001.txt". |
|
112
|
|
|
|
|
113
|
|
|
Returns: |
|
114
|
|
|
List[str]: Skipping files list by exists same name. |
|
115
|
|
|
""" |
|
116
|
1 |
|
count = 0 |
|
117
|
1 |
|
files = self._make_file_list(self.__directory_path) |
|
118
|
|
|
|
|
119
|
1 |
|
_logger.info( |
|
120
|
|
|
"Target directory: {directory_path}".format( |
|
121
|
|
|
directory_path=self.__directory_path |
|
122
|
|
|
) |
|
123
|
|
|
) |
|
124
|
1 |
|
_logger.info("Digit: {digits}".format(digits=self.__digits)) |
|
125
|
1 |
|
_logger.info("Extension: {extension}".format(extension=self.__extension)) |
|
126
|
1 |
|
_logger.info("-" * 55) |
|
127
|
|
|
|
|
128
|
1 |
|
for file in files: |
|
129
|
1 |
|
self._rename_digit_filename(file) |
|
130
|
1 |
|
count += 1 |
|
131
|
|
|
|
|
132
|
1 |
|
_logger.info("-" * 55) |
|
133
|
1 |
|
_logger.info("Finished! Rename file count: {count}".format(count=count)) |
|
134
|
1 |
|
return self.__exist_files |
|
135
|
|
|
|
|
136
|
1 |
|
def async_filename_to_digit_number(self): |
|
137
|
|
|
"""Change file name to only digit name on async. |
|
138
|
|
|
|
|
139
|
|
|
If use this function, a little bit speedy |
|
140
|
|
|
compare with filename_to_digit_number function. |
|
141
|
|
|
|
|
142
|
|
|
Returns: |
|
143
|
|
|
List[str]: Skipping files list by exists same name. |
|
144
|
|
|
""" |
|
145
|
1 |
|
files = self._make_file_list(self.__directory_path) |
|
146
|
|
|
|
|
147
|
1 |
|
_logger.info( |
|
148
|
|
|
"Target directory: {directory_path}".format( |
|
149
|
|
|
directory_path=self.__directory_path |
|
150
|
|
|
) |
|
151
|
|
|
) |
|
152
|
1 |
|
_logger.info("Digit: {digits}".format(digits=self.__digits)) |
|
153
|
1 |
|
_logger.info("Extension: {extension}".format(extension=self.__extension)) |
|
154
|
1 |
|
_logger.info("-" * 55) |
|
155
|
|
|
|
|
156
|
1 |
|
loop = self._get_eventloop() |
|
157
|
1 |
|
queue = self._create_task_queue(files) |
|
158
|
1 |
|
loop.run_until_complete( |
|
159
|
|
|
self._execute_queuing_tasks(queue, loop, None, self._rename_digit_filename) |
|
160
|
|
|
) |
|
161
|
1 |
|
_logger.info("-" * 55) |
|
162
|
1 |
|
_logger.info("Finished! Async rename") |
|
163
|
1 |
|
return self.__exist_files |
|
164
|
|
|
|
|
165
|
1 |
|
def _rename_digit_filename(self, file): |
|
166
|
1 |
|
num = self._check_serial_number(file, self.__digits) |
|
167
|
1 |
|
if not self._check_skip_file(file, self.__regex_ext, num): |
|
168
|
1 |
|
new_name = self._create_new_name(num, self.__max_digit, self.__extension) |
|
169
|
1 |
|
if not self.__check_exist_file(new_name, file, True): |
|
170
|
1 |
|
self._rename_file(file, new_name) |
|
171
|
1 |
|
return True |
|
172
|
|
|
|
|
173
|
1 |
|
def change_name_manually(self, overwrite=False): |
|
174
|
|
|
"""Change filename manually looking exist_file list. |
|
175
|
|
|
|
|
176
|
|
|
After changing file name for filename_to_digit_number() method, |
|
177
|
|
|
duplicate file name change manually. |
|
178
|
|
|
|
|
179
|
|
|
Args: |
|
180
|
|
|
overwrite (bool): If true, overwrite file name even if exist same name file. |
|
181
|
|
|
|
|
182
|
|
|
Returns: |
|
183
|
|
|
bool: If success, return True. |
|
184
|
|
|
|
|
185
|
|
|
""" |
|
186
|
|
|
|
|
187
|
1 |
|
def _flag_yes(): |
|
188
|
1 |
|
new_name_y = self.__input_new_file_name(file, overwrite) |
|
189
|
1 |
|
self._rename_file(file, new_name_y) |
|
190
|
1 |
|
_logger.info( |
|
191
|
|
|
"Rename: {old_name} => {new_name} \n".format( |
|
192
|
|
|
old_name=file, new_name=new_name_y |
|
193
|
|
|
) |
|
194
|
|
|
) |
|
195
|
|
|
|
|
196
|
1 |
|
def _flag_rename(): |
|
197
|
1 |
|
flag_delete = input( |
|
198
|
|
|
"Will be {file} deleted? " "OK? (y/n/c/r)".format(file=file) |
|
199
|
|
|
) # y="Yes" n="No" c="check" r="rename" |
|
200
|
1 |
|
if flag_delete == "c": |
|
201
|
1 |
|
self._check_image_file(file) |
|
202
|
1 |
|
elif flag_delete in ("Y", "y"): |
|
203
|
1 |
|
self._remove_file(file) |
|
204
|
1 |
|
elif flag_delete == "r": |
|
205
|
1 |
|
new_name_rename = self.__input_new_file_name(file, overwrite) |
|
206
|
1 |
|
self._rename_file(file, new_name_rename) |
|
207
|
|
|
else: |
|
208
|
1 |
|
_logger.info("Leave file: {file}\n".format(file=file)) |
|
209
|
|
|
|
|
210
|
1 |
|
_logger.info("-" * 55) |
|
211
|
1 |
|
_logger.info("Manually determine file names duplicated by the serial number\n") |
|
212
|
1 |
|
for file in self.__exist_files: |
|
213
|
1 |
|
_logger.info("File name: {file_name}") |
|
214
|
1 |
|
flag = input( |
|
215
|
|
|
"Does it rename? (y/n/r)".format(file_name=file) |
|
216
|
|
|
) # y="Yes" n="No" r="Remove" |
|
217
|
1 |
|
if flag in ("Y", "y"): |
|
218
|
1 |
|
_flag_yes() |
|
219
|
1 |
|
elif flag == "r": |
|
220
|
1 |
|
try: |
|
221
|
1 |
|
_flag_rename() |
|
222
|
1 |
|
except InvalidImageParameterTypeError as invalid_image_parameter_type: |
|
223
|
1 |
|
_logger.warn(invalid_image_parameter_type) |
|
224
|
1 |
|
_logger.info("Skip..") |
|
225
|
|
|
else: |
|
226
|
1 |
|
_logger.info("Leave file: {file}\n".format(file=file)) |
|
227
|
1 |
|
_logger.info("Finished.") |
|
228
|
1 |
|
return True |
|
229
|
|
|
|
|
230
|
1 |
|
def add_before_after_str(self, before, after): |
|
231
|
|
|
"""Add file name specify string. |
|
232
|
|
|
|
|
233
|
|
|
After changing file name for filename_to_digit_number() method, |
|
234
|
|
|
add specify string before or after file name. |
|
235
|
|
|
|
|
236
|
|
|
Args: |
|
237
|
|
|
before (str): String before file name |
|
238
|
|
|
after (str): String after file name |
|
239
|
|
|
|
|
240
|
|
|
Returns: |
|
241
|
|
|
bool: If success, return True. |
|
242
|
|
|
|
|
243
|
|
|
""" |
|
244
|
1 |
|
_logger.info("-" * 55) |
|
245
|
1 |
|
files = self._make_file_list(self.__directory_path) |
|
246
|
1 |
|
if before is not None: |
|
247
|
1 |
|
_logger.info("Add {before} before serial digit".format(before=before)) |
|
248
|
|
|
else: |
|
249
|
1 |
|
before = "" |
|
250
|
1 |
|
if after is not None: |
|
251
|
1 |
|
_logger.info("Add {after} before serial digit".format(after=after)) |
|
252
|
|
|
else: |
|
253
|
1 |
|
after = "" |
|
254
|
|
|
|
|
255
|
1 |
|
for file in files: |
|
256
|
1 |
|
num = self._check_serial_number(file, self.__digits) |
|
257
|
1 |
|
if not num: |
|
258
|
1 |
|
_logger.debug("Skip(No number): {filename}".format(filename=str(file))) |
|
259
|
|
|
else: |
|
260
|
1 |
|
if self.__regex_ext.search(file): |
|
261
|
1 |
|
_, center, _ = self._split_dir_root_ext(file) |
|
262
|
1 |
|
new_name = before + center + after + self.__extension |
|
263
|
1 |
|
if self.__check_exist_file(new_name, new_name, False): |
|
264
|
1 |
|
pass |
|
265
|
|
|
else: |
|
266
|
1 |
|
self._rename_file(file, new_name) |
|
267
|
|
|
return True |
|
268
|
|
|
|