1
|
|
|
"""Car Music Sorter.""" |
2
|
|
|
import gettext |
3
|
|
|
import os |
4
|
|
|
import re |
5
|
|
|
import shutil |
6
|
|
|
import w_settings |
7
|
|
|
|
8
|
|
|
from sys import exit |
9
|
|
|
from tkinter import Tk, PhotoImage, Menu, LabelFrame |
10
|
|
|
from tkinter import Toplevel |
11
|
|
|
from tkinter.ttk import Button, Label, Progressbar |
12
|
|
|
from pathlib import Path |
13
|
|
|
from f_getconfig import getconfig |
14
|
|
|
from f_logging import writelog |
15
|
|
|
|
16
|
|
|
import tkinter.filedialog as fd |
17
|
|
|
input_dir = '' |
18
|
|
|
output_dir = '' |
19
|
|
|
source_file = [] |
20
|
|
|
|
21
|
|
|
|
22
|
|
|
# BEGIN FUNCTIONS # |
23
|
|
|
# FIXME: Вынести по возможности в отдельные файлы |
24
|
|
|
# Определение исходной и целевой директорий |
25
|
|
|
def workdirs(param): |
26
|
|
|
"""Открывает диалог выбора директории.""" |
27
|
|
|
if param == 'indir': |
28
|
|
|
global input_dir |
29
|
|
|
input_dir = fd.askdirectory(title = _('Open source directory')) |
|
|
|
|
30
|
|
|
if input_dir != '': |
31
|
|
|
source_label.config(text = f'...{path_short(input_dir, 2)}') |
32
|
|
|
writelog(_('Input DIR set to: ') + input_dir) |
33
|
|
|
else: |
34
|
|
|
input_dir = '' |
35
|
|
|
elif param == 'outdir': |
36
|
|
|
global output_dir |
37
|
|
|
output_dir = fd.askdirectory(title = _('Set destination directory')) |
38
|
|
|
if output_dir != '': |
39
|
|
|
dest_label.config(text = f'...{path_short(output_dir, 2)}') |
40
|
|
|
writelog(_('Output DIR set to: ') + output_dir) |
41
|
|
|
else: |
42
|
|
|
output_dir = '' |
43
|
|
|
elif param == 'clear': |
44
|
|
|
source_label.config(text = _('Input DIR not defined')) |
45
|
|
|
dest_label.config(text = _('Output DIR not defined')) |
46
|
|
|
main_progressbar['value'] = 0 |
47
|
|
|
input_dir = output_dir = '' |
48
|
|
|
|
49
|
|
|
|
50
|
|
|
def path_short(path_string, len): |
51
|
|
|
"""Сокращает путь для корректного отображения в лейбле.""" |
52
|
|
|
return Path(*Path(path_string).parts[-len:]) |
53
|
|
|
|
54
|
|
|
|
55
|
|
|
# Вызов "О программе" |
56
|
|
|
def popup_about(vers): |
57
|
|
|
"""Открывает окно 'О программе'.""" |
58
|
|
|
# Центровка окна |
59
|
|
|
main_width = 400 |
60
|
|
|
main_height = 150 |
61
|
|
|
center_x_pos = int(window.winfo_screenwidth() / 2) - main_width |
62
|
|
|
center_y_pos = int(window.winfo_screenheight() / 2) - main_height |
63
|
|
|
|
64
|
|
|
popup = Toplevel() |
65
|
|
|
popup.geometry(f'{main_width}x{main_height}+{center_x_pos}+{center_y_pos}') |
66
|
|
|
popup.title(_('About')) |
|
|
|
|
67
|
|
|
imagepath = 'data/imgs/main.png' |
68
|
|
|
img = PhotoImage(file = imagepath) |
69
|
|
|
poplabel1 = Label(popup, image = img) |
70
|
|
|
poplabel1.grid(sticky = 'W', column = 0, row = 0, rowspan = 2) |
71
|
|
|
|
72
|
|
|
name_vers_str = 'Car Music Sorter\n\n' + _('Version: ') + vers |
73
|
|
|
author_github = 'https://github.com/intervisionlord' |
74
|
|
|
prog_author = _('\nAuthor: ') + 'Intervision\nGithub: ' + author_github |
75
|
|
|
poplabel_maindesc = Label(popup, |
76
|
|
|
text = name_vers_str + prog_author, |
77
|
|
|
justify = 'left') |
78
|
|
|
poplabel_maindesc.grid(sticky = 'W', column = 1, row = 0) |
79
|
|
|
# Автор иконок |
80
|
|
|
icons_author = _('Icons: ') + 'icon king1 ' + _('on') + ' freeicons.io' |
81
|
|
|
poplabel_icons = Label(popup, text = icons_author, justify = 'left') |
82
|
|
|
poplabel_icons.grid(sticky = 'W', column = 1, row = 1) |
83
|
|
|
|
84
|
|
|
popup.grab_set() |
85
|
|
|
popup.focus_set() |
86
|
|
|
popup.wait_window() |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
# Основные операции |
90
|
|
|
def check_paths(): |
91
|
|
|
"""Проверяет, что все пути заданы корректно и запускает копирование.""" |
92
|
|
|
if input_dir == '' or output_dir == '': |
93
|
|
|
writelog(_('Input DIR or Output DIR are not defined!')) |
|
|
|
|
94
|
|
|
elif input_dir == output_dir: |
95
|
|
|
writelog(_('Input DIR and Output DIR must be different!')) |
96
|
|
|
return |
97
|
|
|
else: |
98
|
|
|
for path, subdirs, files in os.walk(input_dir): |
99
|
|
|
for file in files: |
100
|
|
|
# Перегоняем MP3 без лайвов и ремиксов в целевую директорию. |
101
|
|
|
filtered = re.search(r'^(?!(.*[Rr]emix.*|.*[Ll]ive.*)).*mp3', |
102
|
|
|
file) |
103
|
|
|
if filtered is not None: |
104
|
|
|
source_file.append(f'{path}/{filtered.group(0)}') |
105
|
|
|
main_progressbar['maximum'] = len(source_file) |
106
|
|
|
for files in source_file: |
107
|
|
|
maincopy(files, output_dir) |
108
|
|
|
source_file.clear() |
109
|
|
|
|
110
|
|
|
|
111
|
|
|
def processing(): |
112
|
|
|
"""Удаляет ремиксы и лайвы.""" |
113
|
|
|
check_paths() |
114
|
|
|
# Удаление ремиксов и лайвов |
115
|
|
|
# TODO: Depricated |
116
|
|
|
liveregexp = r'.*\(.*[Rr]emix.*\).*|.*\(.*[Ll]ive.*\).*' |
117
|
|
|
for files in os.walk(output_dir): |
118
|
|
|
for file in files[2]: |
119
|
|
|
try: |
120
|
|
|
source_file.append(re.search(liveregexp, file).group(0)) |
121
|
|
|
except Exception: |
122
|
|
|
pass |
123
|
|
|
for file in source_file: |
124
|
|
|
writelog(_('Removing Remix: ') + file) |
|
|
|
|
125
|
|
|
os.remove(f'{output_dir}/{file}') |
126
|
|
|
main_progressbar['value'] = main_progressbar['value'] + 1 |
|
|
|
|
127
|
|
|
window.update_idletasks() |
128
|
|
|
source_file.clear() # Очищаем список |
129
|
|
|
polish_filenames() |
130
|
|
|
|
131
|
|
|
|
132
|
|
|
def polish_filenames(): |
133
|
|
|
"""Удаляет из имен треков мусор.""" |
134
|
|
|
# Готовим список свежепринесенных файлов с вычищенными ремиксами и лайвами |
135
|
|
|
for files in os.walk(output_dir): |
136
|
|
|
for file in files[2]: |
137
|
|
|
try: |
138
|
|
|
source_file.append(file) |
139
|
|
|
except Exception: |
140
|
|
|
pass |
141
|
|
|
|
142
|
|
|
# Убираем из имен файлов мусор (номера треков в различном формате) |
143
|
|
|
main_progressbar['maximum'] = (main_progressbar['maximum'] + |
|
|
|
|
144
|
|
|
len(source_file)) |
145
|
|
|
trashregexp = r'^[\d{1,2}\s\-\.]*' |
146
|
|
|
for file in source_file: |
147
|
|
|
new_file = re.sub(trashregexp, '', file) |
148
|
|
|
shutil.move(f'{output_dir}/{file}', f'{output_dir}/{new_file}') |
149
|
|
|
main_progressbar['value'] = main_progressbar['value'] + 1 |
150
|
|
|
window.update_idletasks() |
151
|
|
|
source_file.clear() |
152
|
|
|
writelog(_('Completed!')) |
|
|
|
|
153
|
|
|
|
154
|
|
|
|
155
|
|
|
# Копируем файлы |
156
|
|
|
def maincopy(files, output_dir): |
157
|
|
|
"""Копирует файлы.""" |
158
|
|
|
writelog(f'{files}') |
159
|
|
|
filename = str.split(files, '/') |
160
|
|
|
writelog(filename[-1]) |
161
|
|
|
shutil.copyfile(f'{files}', f'{output_dir}/{filename[-1]}') |
162
|
|
|
main_progressbar['value'] = main_progressbar['value'] + 1 |
|
|
|
|
163
|
|
|
window.update_idletasks() |
164
|
|
|
# END FUNCTIONS # |
165
|
|
|
|
166
|
|
|
|
167
|
|
|
# Вводим основные переменные |
168
|
|
|
vers = getconfig()['core']['version'] |
169
|
|
|
langcode = getconfig()['settings']['locale'] |
170
|
|
|
# Локализация |
171
|
|
|
gettext.translation('CarMusicSorter', localedir='l10n', |
172
|
|
|
languages=[langcode]).install() |
173
|
|
|
writelog('init') |
174
|
|
|
# Рисуем окно |
175
|
|
|
window = Tk() |
176
|
|
|
window.iconphoto(True, PhotoImage(file = 'data/imgs/main.png')) |
177
|
|
|
window.geometry('370x270') |
178
|
|
|
window.eval('tk::PlaceWindow . center') |
179
|
|
|
window.title('Car Music Sorter') |
180
|
|
|
window.resizable(False, False) |
181
|
|
|
|
182
|
|
|
# Пути к оформлению |
183
|
|
|
sourceicon = PhotoImage(file = 'data/imgs/20source.png') |
184
|
|
|
desticon = PhotoImage(file = 'data/imgs/20dest.png') |
185
|
|
|
launchicon = PhotoImage(file = 'data/imgs/20ok.png') |
186
|
|
|
clearicon = PhotoImage(file = 'data/imgs/20clear.png') |
187
|
|
|
|
188
|
|
|
# Основное меню |
189
|
|
|
menu = Menu(window) |
190
|
|
|
menu_about = Menu(menu, tearoff = 0) |
191
|
|
|
menu_file = Menu(menu, tearoff = 0) |
192
|
|
|
menu.add_cascade(label = _('File'), menu = menu_file) |
|
|
|
|
193
|
|
|
menu.add_cascade(label = _('Info'), menu = menu_about) |
194
|
|
|
|
195
|
|
|
# Элементы меню |
196
|
|
|
menu_about.add_command(label = _('About'), |
197
|
|
|
command = lambda: popup_about(vers), |
198
|
|
|
accelerator = 'F1') |
199
|
|
|
menu_file.add_command(label = _('Input Dir'), |
200
|
|
|
command = lambda: workdirs('indir'), |
201
|
|
|
accelerator = 'CTRL+O') |
202
|
|
|
menu_file.add_command(label = _('Output Dir'), |
203
|
|
|
command = lambda: workdirs('outdirs'), |
204
|
|
|
accelerator = 'CTRL+D') |
205
|
|
|
menu_file.add_command(label = _('Clear'), |
206
|
|
|
command = lambda: workdirs('clear'), |
207
|
|
|
accelerator = 'CTRL+R') |
208
|
|
|
menu_file.add_separator() |
209
|
|
|
menu_file.add_command(label = _('Settings'), |
210
|
|
|
command = w_settings.popup_settings) |
211
|
|
|
menu_file.add_separator() |
212
|
|
|
menu_file.add_command(label = _('Exit'), |
213
|
|
|
command = exit, |
214
|
|
|
accelerator = 'CTRL+E') |
215
|
|
|
|
216
|
|
|
# Биндим хоткеи к функциям |
217
|
|
|
menu_file.bind_all('<Command-o>', lambda event: workdirs('indir')) |
218
|
|
|
menu_file.bind_all('<Command-d>', lambda event: workdirs('outdir')) |
219
|
|
|
menu_file.bind_all('<Command-r>', lambda event: workdirs('clear')) |
220
|
|
|
menu_file.bind_all('<Command-e>', exit) |
221
|
|
|
|
222
|
|
|
menu_about.bind_all('<F1>', lambda event: popup_about(vers)) |
223
|
|
|
window.config(menu = menu) |
224
|
|
|
|
225
|
|
|
# Строим элеметны основного окна и группы |
226
|
|
|
first_group = LabelFrame(window, text = _('IO Directories')) |
227
|
|
|
|
228
|
|
|
first_group.grid(sticky = 'WE', column = 0, row = 0, padx = 5, pady = 10, |
229
|
|
|
ipadx = 2, ipady = 4) |
230
|
|
|
|
231
|
|
|
operation_group = LabelFrame(window, text = _('Operations')) |
232
|
|
|
operation_group.grid(sticky = 'WE', column = 0, row = 3, padx = 5, pady = 5, |
233
|
|
|
ipadx = 5, ipady = 5) |
234
|
|
|
|
235
|
|
|
progress_group = LabelFrame(window, text = _('Progress')) |
236
|
|
|
progress_group.grid(sticky = 'WE', column = 0, row = 1, padx = 5, pady = 5, |
237
|
|
|
ipadx = 0, ipady = 2, rowspan = 2) |
238
|
|
|
|
239
|
|
|
# Прогрессбар |
240
|
|
|
main_progressbar = Progressbar(progress_group, length = 350, value = 0, |
241
|
|
|
orient = 'horizontal', mode = 'determinate') |
242
|
|
|
main_progressbar.grid(pady = 4, column = 0, row = 1) |
243
|
|
|
|
244
|
|
|
# Поясняющие лейблы |
245
|
|
|
source_label_text = _('Input DIR not defined') |
246
|
|
|
dest_label_text = _('Output DIR not defined') |
247
|
|
|
source_label = Label(first_group, text = source_label_text, justify = 'left') |
248
|
|
|
source_label.grid(column = 1, row = 0) |
249
|
|
|
dest_label = Label(first_group, text = dest_label_text, justify = 'left') |
250
|
|
|
dest_label.grid(column = 1, row = 1) |
251
|
|
|
|
252
|
|
|
# Кнопки |
253
|
|
|
source_button = Button(first_group, text = _('Input Dir'), |
254
|
|
|
command = lambda: workdirs('indir'), image = sourceicon, |
255
|
|
|
width = 20, compound = 'left') |
256
|
|
|
source_button.grid(row = 0, ipadx = 2, ipady = 2, padx = 4) |
257
|
|
|
|
258
|
|
|
dest_button = Button(first_group, text = _('Output Dir'), |
259
|
|
|
command = lambda: workdirs('outdir'), image = desticon, |
260
|
|
|
width = 20, compound = 'left') |
261
|
|
|
dest_button.grid(row = 1, ipadx = 2, ipady = 2, padx = 4) |
262
|
|
|
|
263
|
|
|
launch_button = Button(operation_group, text = _('Process'), |
264
|
|
|
command = processing, image = launchicon, |
265
|
|
|
width = 20, compound = 'left') |
266
|
|
|
launch_button.grid(column = 0, row = 2, ipadx = 2, ipady = 2, padx = 12) |
267
|
|
|
|
268
|
|
|
clear_button = Button(operation_group, text = _('Clear'), |
269
|
|
|
command = lambda: workdirs('clear'), image = clearicon, |
270
|
|
|
width = 20, compound = 'left') |
271
|
|
|
|
272
|
|
|
clear_button.grid(column = 1, row = 2, ipadx = 2, ipady = 2, padx = 0) |
273
|
|
|
|
274
|
|
|
if __name__ == '__main__': |
275
|
|
|
window.mainloop() |
276
|
|
|
|