From db5c323003464c8c5a93bf487a4e4e1dbd58e7e7 Mon Sep 17 00:00:00 2001 From: Dilan Boskan Date: Mon, 9 Nov 2020 12:10:35 +0100 Subject: [PATCH] Added Drag & Drop & Bug fixes - Added Drag & Drop for the save to directory and the music file selection (#2) - Fixed Model Names not decoding correctly --- VocalRemover.py | 166 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 54 deletions(-) diff --git a/VocalRemover.py b/VocalRemover.py index 37f67f2..d6d0a0b 100644 --- a/VocalRemover.py +++ b/VocalRemover.py @@ -4,6 +4,7 @@ import tkinter.ttk as ttk import tkinter.messagebox import tkinter.filedialog import tkinter.font +from tkinterdnd2 import TkinterDnD, DND_FILES # Enable Drag & Drop from datetime import datetime # Images from PIL import Image @@ -168,6 +169,30 @@ def get_model_values(model_name): return model_values +def drop(var, event, accept_mode: str = 'files'): + """ + Drag & Drop verification process + """ + path = event.data + + if accept_mode == 'folder': + path = path.replace('{', '').replace('}', '') + if not os.path.isdir(path): + tk.messagebox.showerror(title='Invalid Folder', + message='Your given export path is not a valid folder!') + return + elif accept_mode == 'files': + # Clean path text and set path to the list of paths + path = path[:-1] + path = path.replace('{', '') + path = path.split('} ') + else: + # Invalid accept mode + return + + var.set(path) + + class ThreadSafeConsole(tk.Text): """ Text Widget which is thread safe for tkinter @@ -201,7 +226,7 @@ class ThreadSafeConsole(tk.Text): self.after(100, self.update_me) -class MainWindow(tk.Tk): +class MainWindow(TkinterDnD.Tk): # --Constants-- # Layout IMAGE_HEIGHT = 140 @@ -233,6 +258,7 @@ class MainWindow(tk.Tk): xpad=int(self.winfo_screenwidth()/2 - 550/2), ypad=int(self.winfo_screenheight()/2 - height/2 - 30))) self.configure(bg='#000000') # Set background color to black + self.protocol("WM_DELETE_WINDOW", self.save_values) self.resizable(False, False) self.update() @@ -249,7 +275,7 @@ class MainWindow(tk.Tk): data = load_data() # Paths self.exportPath_var = tk.StringVar(value=data['export_path']) - self.inputPaths = [] + self.inputPaths_var = tk.StringVar(value='') # Processing Options self.gpuConversion_var = tk.BooleanVar(value=data['gpu']) self.postprocessing_var = tk.BooleanVar(value=data['postprocess']) @@ -280,6 +306,7 @@ class MainWindow(tk.Tk): # --Widgets-- self.create_widgets() self.configure_widgets() + self.bind_widgets() self.place_widgets() self.update_available_models() @@ -321,6 +348,21 @@ class MainWindow(tk.Tk): font=self.font, foreground='white') ttk.Style().configure('T', font=self.font, foreground='white') + def bind_widgets(self): + """Bind widgets to the drag & drop mechanic""" + self.filePaths_saveTo_Button.drop_target_register(DND_FILES) + self.filePaths_saveTo_Entry.drop_target_register(DND_FILES) + self.filePaths_musicFile_Button.drop_target_register(DND_FILES) + self.filePaths_musicFile_Entry.drop_target_register(DND_FILES) + self.filePaths_saveTo_Button.dnd_bind('<>', + lambda e, var=self.exportPath_var: drop(var, e, accept_mode='folder')) + self.filePaths_saveTo_Entry.dnd_bind('<>', + lambda e, var=self.exportPath_var: drop(var, e, accept_mode='folder')) + self.filePaths_musicFile_Button.dnd_bind('<>', + lambda e, var=self.inputPaths_var: drop(var, e, accept_mode='files')) + self.filePaths_musicFile_Entry.dnd_bind('<>', + lambda e, var=self.inputPaths_var: drop(var, e, accept_mode='files')) + def place_widgets(self): """Place main widgets""" self.title_Label.place(x=-2, y=-2) @@ -354,7 +396,7 @@ class MainWindow(tk.Tk): text='Select Your Audio File(s)', command=self.open_file_filedialog) self.filePaths_musicFile_Entry = ttk.Entry(master=self.filePaths_Frame, - text=self.inputPaths, + textvariable=self.inputPaths_var, state=tk.DISABLED ) # -Place Widgets- @@ -557,13 +599,7 @@ class MainWindow(tk.Tk): initialdir=self.lastDir, ) if paths: # Path selected - self.inputPaths = paths - # Change the entry text - self.filePaths_musicFile_Entry.configure(state=tk.NORMAL) - self.filePaths_musicFile_Entry.delete(0, tk.END) - self.filePaths_musicFile_Entry.insert(0, self.inputPaths) - self.filePaths_musicFile_Entry.configure(state=tk.DISABLED) - + self.inputPaths_var.set(paths) self.lastDir = os.path.dirname(paths[0]) def open_export_filedialog(self): @@ -584,6 +620,7 @@ class MainWindow(tk.Tk): """ # -Get all variables- export_path = self.exportPath_var.get() + input_paths = self.inputPaths_var.get() instrumentalModel_path = self.instrumentalLabel_to_path[self.instrumentalModel_var.get()] # nopep8 stackedModel_path = self.stackedLabel_to_path[self.stackedModel_var.get()] # nopep8 # Get constants @@ -613,12 +650,13 @@ class MainWindow(tk.Tk): return # -Check for invalid inputs- - if not any([(os.path.isfile(path) and path.endswith(('.mp3', '.mp4', '.m4a', '.flac', '.wav'))) - for path in self.inputPaths]): - tk.messagebox.showwarning(master=self, - title='Invalid Music File', - message='You have selected an invalid music file!\nPlease make sure that your files still exist and ends with either ".mp3", ".mp4", ".m4a", ".flac", ".wav"') - return + for path in input_paths: + if not os.path.isfile(path): + tk.messagebox.showwarning(master=self, + title='Invalid Music File', + message='You have selected an invalid music file! Please make sure that the file still exists!', + detail=f'File path: {path}') + return if not os.path.isdir(export_path): tk.messagebox.showwarning(master=self, title='Invalid Export Directory', @@ -638,27 +676,6 @@ class MainWindow(tk.Tk): message='You have selected an invalid stacked model file!\nPlease make sure that your model file still exists!') return - # -Save Data- - save_data(data={ - 'export_path': export_path, - 'gpu': self.gpuConversion_var.get(), - 'postprocess': self.postprocessing_var.get(), - 'tta': self.tta_var.get(), - 'output_image': self.outputImage_var.get(), - 'stack': self.stack_var.get(), - 'stackOnly': self.stackOnly_var.get(), - 'stackPasses': stackPasses, - 'saveAllStacked': self.saveAllStacked_var.get(), - 'sr': sr, - 'hop_length': hop_length, - 'window_size': window_size, - 'n_fft': n_fft, - 'useModel': 'instrumental', # Always instrumental - 'lastDir': self.lastDir, - 'modelFolder': self.modelFolder_var.get(), - 'aiModel': self.aiModel_var.get(), - }) - if self.aiModel_var.get() == 'v2': inference = inference_v2 elif self.aiModel_var.get() == 'v4': @@ -670,7 +687,7 @@ class MainWindow(tk.Tk): threading.Thread(target=inference.main, kwargs={ # Paths - 'input_paths': self.inputPaths, + 'input_paths': input_paths, 'export_path': export_path, # Processing Options 'gpu': 0 if self.gpuConversion_var.get() else -1, @@ -726,20 +743,19 @@ class MainWindow(tk.Tk): # Loop through each constant (key) and its widgets for key, (widget, var) in widgetsVars.items(): if stacked_selectable: - # Stacked model can be selected - if key in stacked.keys(): - if (key in stacked.keys() and - not instrumental_selectable): - # Only stacked selectable - widget.configure(state=tk.DISABLED) - var.set(stacked[key]) - continue - elif (key in instrumental.keys() and - instrumental_selectable): + if instrumental_selectable: + if (key in instrumental.keys() and + key in stacked.keys()): # Both models have set constants widget.configure(state=tk.DISABLED) var.set('%d/%d' % (instrumental[key], stacked[key])) continue + else: + if key in stacked.keys(): + # Only stacked selectable + widget.configure(state=tk.DISABLED) + var.set(stacked[key]) + continue else: # Stacked model can not be selected if (key in instrumental.keys() and @@ -837,7 +853,6 @@ class MainWindow(tk.Tk): # Instrumental Model self.options_instrumentalModel_Label.configure(foreground='#000') self.options_instrumentalModel_Optionmenu.configure(state=tk.NORMAL) # nopep8 - self.instrumentalModel_var.set('') # Stack Model if stackLoops > 0: @@ -885,11 +900,54 @@ class MainWindow(tk.Tk): """ Restart the application after asking for confirmation """ - proceed = tk.messagebox.askyesno(title='Confirmation', - message='The application will restart and lose unsaved data. Do you wish to proceed?') - if proceed: - subprocess.Popen(f'python "{__file__}"', shell=True) - exit() + save = tk.messagebox.askyesno(title='Confirmation', + message='The application will restart. Do you want to save the data?') + if save: + self.save_values() + subprocess.Popen(f'python "{__file__}"', shell=True) + exit() + + def save_values(self): + """ + Save the data of the application + """ + export_path = self.exportPath_var.get() + # Get constants + instrumental = get_model_values(self.instrumentalModel_var.get()) + stacked = get_model_values(self.stackedModel_var.get()) + if [bool(instrumental), bool(stacked)].count(True) == 2: + sr = DEFAULT_DATA['sr'] + hop_length = DEFAULT_DATA['hop_length'] + window_size = DEFAULT_DATA['window_size'] + n_fft = DEFAULT_DATA['n_fft'] + else: + sr = self.srValue_var.get() + hop_length = self.hopValue_var.get() + window_size = self.winSize_var.get() + n_fft = self.nfft_var.get() + + # -Save Data- + save_data(data={ + 'export_path': export_path, + 'gpu': self.gpuConversion_var.get(), + 'postprocess': self.postprocessing_var.get(), + 'tta': self.tta_var.get(), + 'output_image': self.outputImage_var.get(), + 'stack': self.stack_var.get(), + 'stackOnly': self.stackOnly_var.get(), + 'stackPasses': self.stackLoops_var.get(), + 'saveAllStacked': self.saveAllStacked_var.get(), + 'sr': sr, + 'hop_length': hop_length, + 'window_size': window_size, + 'n_fft': n_fft, + 'useModel': 'instrumental', + 'lastDir': self.lastDir, + 'modelFolder': self.modelFolder_var.get(), + 'aiModel': self.aiModel_var.get(), + }) + + self.destroy() if __name__ == "__main__":