From bc5195e68c3ea545b28d83748d7f602139ba174c Mon Sep 17 00:00:00 2001 From: Anjok07 <68268275+Anjok07@users.noreply.github.com> Date: Tue, 10 May 2022 19:11:40 -0500 Subject: [PATCH] Add files via upload --- UVR.py | 1702 +++++++++++++++++++++++++++++++ inference_MDX.py | 1024 +++++++++++++++++++ inference_v5.py | 1022 +++++++++++++++++++ inference_v5_ensemble.py | 2049 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 5797 insertions(+) create mode 100644 UVR.py create mode 100644 inference_MDX.py create mode 100644 inference_v5.py create mode 100644 inference_v5_ensemble.py diff --git a/UVR.py b/UVR.py new file mode 100644 index 0000000..8b6360a --- /dev/null +++ b/UVR.py @@ -0,0 +1,1702 @@ +# GUI modules +import os +import pyperclip +from gc import freeze +import tkinter as tk +from tkinter import * +from tkinter.tix import * +import webbrowser +from tracemalloc import stop +import lib_v5.sv_ttk +import tkinter.ttk as ttk +import tkinter.messagebox +import tkinter.filedialog +import tkinter.font +from tkinterdnd2 import TkinterDnD, DND_FILES # Enable Drag & Drop +import pyglet,tkinter +from datetime import datetime +# Images +from PIL import Image +from PIL import ImageTk +import pickle # Save Data +# Other Modules + +# Pathfinding +import pathlib +import sys +import subprocess +from collections import defaultdict +# Used for live text displaying +import queue +import threading # Run the algorithm inside a thread +from subprocess import call +from pathlib import Path +import ctypes as ct +import subprocess # Run python file +import inference_MDX +import inference_v5 +import inference_v5_ensemble + +try: + with open(os.path.join(os.getcwd(), 'tmp', 'splash.txt'), 'w') as f: + f.write('1') +except: + pass + +# Change the current working directory to the directory +# this file sits in +if getattr(sys, 'frozen', False): + # If the application is run as a bundle, the PyInstaller bootloader + # extends the sys module by a flag frozen=True and sets the app + # path into variable _MEIPASS'. + base_path = sys._MEIPASS +else: + base_path = os.path.dirname(os.path.abspath(__file__)) + +os.chdir(base_path) # Change the current working directory to the base path + +#Images +instrumentalModels_dir = os.path.join(base_path, 'models') +banner_path = os.path.join(base_path, 'img', 'UVR-banner.png') +efile_path = os.path.join(base_path, 'img', 'file.png') +stop_path = os.path.join(base_path, 'img', 'stop.png') +help_path = os.path.join(base_path, 'img', 'help.png') +gen_opt_path = os.path.join(base_path, 'img', 'gen_opt.png') +mdx_opt_path = os.path.join(base_path, 'img', 'mdx_opt.png') +vr_opt_path = os.path.join(base_path, 'img', 'vr_opt.png') +ense_opt_path = os.path.join(base_path, 'img', 'ense_opt.png') +user_ens_opt_path = os.path.join(base_path, 'img', 'user_ens_opt.png') +more_info_path = os.path.join(base_path, 'img', 'more_info.png') +credits_path = os.path.join(base_path, 'img', 'credits.png') + +DEFAULT_DATA = { + 'exportPath': '', + 'inputPaths': [], + 'saveFormat': 'Wav', + 'gpu': False, + 'postprocess': False, + 'tta': False, + 'save': True, + 'output_image': False, + 'window_size': '512', + 'agg': 10, + 'modelFolder': False, + 'modelInstrumentalLabel': '', + 'aiModel': 'MDX-Net', + 'algo': 'Instrumentals (Min Spec)', + 'ensChoose': 'MDX-Net/VR Ensemble', + 'useModel': 'instrumental', + 'lastDir': None, + 'break': False, + #MDX-Net + 'demucsmodel': True, + 'non_red': False, + 'noise_reduc': True, + 'voc_only': False, + 'inst_only': False, + 'chunks': 'Auto', + 'noisereduc_s': '3', + 'mixing': 'default', + 'mdxnetModel': 'UVR-MDX-NET 1', +} + +def open_image(path: str, size: tuple = None, keep_aspect: bool = True, rotate: int = 0) -> ImageTk.PhotoImage: + """ + Open the image on the path and apply given settings\n + Paramaters: + path(str): + Absolute path of the image + size(tuple): + first value - width + second value - height + keep_aspect(bool): + keep aspect ratio of image and resize + to maximum possible width and height + (maxima are given by size) + rotate(int): + clockwise rotation of image + Returns(ImageTk.PhotoImage): + Image of path + """ + img = Image.open(path).convert(mode='RGBA') + ratio = img.height/img.width + img = img.rotate(angle=-rotate) + if size is not None: + size = (int(size[0]), int(size[1])) + if keep_aspect: + img = img.resize((size[0], int(size[0] * ratio)), Image.ANTIALIAS) + else: + img = img.resize(size, Image.ANTIALIAS) + return ImageTk.PhotoImage(img) + +def save_data(data): + """ + Saves given data as a .pkl (pickle) file + + Paramters: + data(dict): + Dictionary containing all the necessary data to save + """ + # Open data file, create it if it does not exist + with open('data.pkl', 'wb') as data_file: + pickle.dump(data, data_file) + +def load_data() -> dict: + """ + Loads saved pkl file and returns the stored data + + Returns(dict): + Dictionary containing all the saved data + """ + try: + with open('data.pkl', 'rb') as data_file: # Open data file + data = pickle.load(data_file) + + return data + except (ValueError, FileNotFoundError): + # Data File is corrupted or not found so recreate it + save_data(data=DEFAULT_DATA) + + return load_data() + +def drop(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 + # Set Variables + root.exportPath_var.set(path) + elif accept_mode == 'files': + # Clean path text and set path to the list of paths + path = path.replace('{', '') + path = path.split('} ') + path[-1] = path[-1].replace('}', '') + # Set Variables + root.inputPaths = path + root.update_inputPaths() + else: + # Invalid accept mode + return + +class ThreadSafeConsole(tk.Text): + """ + Text Widget which is thread safe for tkinter + """ + def __init__(self, master, **options): + tk.Text.__init__(self, master, **options) + self.queue = queue.Queue() + self.update_me() + + def write(self, line): + self.queue.put(line) + + def clear(self): + self.queue.put(None) + + def update_me(self): + self.configure(state=tk.NORMAL) + try: + while 1: + line = self.queue.get_nowait() + if line is None: + self.delete(1.0, tk.END) + else: + self.insert(tk.END, str(line)) + self.see(tk.END) + self.update_idletasks() + except queue.Empty: + pass + self.configure(state=tk.DISABLED) + self.after(100, self.update_me) + +class MainWindow(TkinterDnD.Tk): + # --Constants-- + # Layout + IMAGE_HEIGHT = 140 + FILEPATHS_HEIGHT = 85 + OPTIONS_HEIGHT = 275 + CONVERSIONBUTTON_HEIGHT = 35 + COMMAND_HEIGHT = 200 + PROGRESS_HEIGHT = 30 + PADDING = 10 + + COL1_ROWS = 11 + COL2_ROWS = 11 + + def __init__(self): + # Run the __init__ method on the tk.Tk class + super().__init__() + + # Calculate window height + height = self.IMAGE_HEIGHT + self.FILEPATHS_HEIGHT + self.OPTIONS_HEIGHT + height += self.CONVERSIONBUTTON_HEIGHT + self.COMMAND_HEIGHT + self.PROGRESS_HEIGHT + height += self.PADDING * 5 # Padding + + # --Window Settings-- + self.title('Ultimate Vocal Remover') + # Set Geometry and Center Window + self.geometry('{width}x{height}+{xpad}+{ypad}'.format( + width=620, + height=height, + xpad=int(self.winfo_screenwidth()/2 - 635/2), + ypad=int(self.winfo_screenheight()/2 - height/2 - 30))) + self.configure(bg='#0e0e0f') # Set background color to #0c0c0d + self.protocol("WM_DELETE_WINDOW", self.save_values) + self.resizable(False, False) + self.update() + + # --Variables-- + self.logo_img = open_image(path=banner_path, + size=(self.winfo_width(), 9999)) + self.efile_img = open_image(path=efile_path, + size=(20, 20)) + self.stop_img = open_image(path=stop_path, + size=(20, 20)) + self.help_img = open_image(path=help_path, + size=(20, 20)) + self.gen_opt_img = open_image(path=gen_opt_path, + size=(1016, 826)) + self.mdx_opt_img = open_image(path=mdx_opt_path, + size=(1016, 826)) + self.vr_opt_img = open_image(path=vr_opt_path, + size=(1016, 826)) + self.ense_opt_img = open_image(path=ense_opt_path, + size=(1016, 826)) + self.user_ens_opt_img = open_image(path=user_ens_opt_path, + size=(1016, 826)) + self.more_info_img = open_image(path=more_info_path, + size=(1016, 826)) + self.credits_img = open_image(path=credits_path, + size=(100, 100)) + + self.instrumentalLabel_to_path = defaultdict(lambda: '') + self.lastInstrumentalModels = [] + + # -Tkinter Value Holders- + data = load_data() + # Paths + self.inputPaths = data['inputPaths'] + self.inputPathop_var = tk.StringVar(value=data['inputPaths']) + self.exportPath_var = tk.StringVar(value=data['exportPath']) + self.saveFormat_var = tk.StringVar(value=data['saveFormat']) + + # Processing Options + self.gpuConversion_var = tk.BooleanVar(value=data['gpu']) + self.postprocessing_var = tk.BooleanVar(value=data['postprocess']) + self.tta_var = tk.BooleanVar(value=data['tta']) + self.save_var = tk.BooleanVar(value=data['save']) + self.outputImage_var = tk.BooleanVar(value=data['output_image']) + # MDX-NET Specific Processing Options + self.demucsmodel_var = tk.BooleanVar(value=data['demucsmodel']) + self.non_red_var = tk.BooleanVar(value=data['non_red']) + self.noisereduc_var = tk.BooleanVar(value=data['noise_reduc']) + self.chunks_var = tk.StringVar(value=data['chunks']) + self.noisereduc_s_var = tk.StringVar(value=data['noisereduc_s']) + self.mixing_var = tk.StringVar(value=data['mixing']) #dropdown + # Models + self.instrumentalModel_var = tk.StringVar(value=data['modelInstrumentalLabel']) + # Model Test Mode + self.modelFolder_var = tk.BooleanVar(value=data['modelFolder']) + # Constants + self.winSize_var = tk.StringVar(value=data['window_size']) + self.agg_var = tk.StringVar(value=data['agg']) + # Instrumental or Vocal Only + self.voc_only_var = tk.BooleanVar(value=data['voc_only']) + self.inst_only_var = tk.BooleanVar(value=data['inst_only']) + # Choose Conversion Method + self.aiModel_var = tk.StringVar(value=data['aiModel']) + self.last_aiModel = self.aiModel_var.get() + # Choose Conversion Method + self.algo_var = tk.StringVar(value=data['algo']) + self.last_algo = self.aiModel_var.get() + # Choose Ensemble + self.ensChoose_var = tk.StringVar(value=data['ensChoose']) + self.last_ensChoose = self.ensChoose_var.get() + # Choose MDX-NET Model + self.mdxnetModel_var = tk.StringVar(value=data['mdxnetModel']) + self.last_mdxnetModel = self.mdxnetModel_var.get() + # Other + self.inputPathsEntry_var = tk.StringVar(value='') + self.lastDir = data['lastDir'] # nopep8 + self.progress_var = tk.IntVar(value=0) + # Font + pyglet.font.add_file('lib_v5/fonts/centurygothic/GOTHIC.TTF') + pyglet.font.add_file('lib_v5/fonts/unispace/unispace.ttf') + #Font(file="lib_v5/fonts/centurygothic/GOTHIC.TTF", family="Century Gothic") + #Font(file="lib_v5/fonts/unispace/unispace.ttf", family="Unispace") + self.font = tk.font.Font(family='Century Gothic', size=10) + self.fontRadio = tk.font.Font(family='Century Gothic', size=8) + # --Widgets-- + self.create_widgets() + self.configure_widgets() + self.bind_widgets() + self.place_widgets() + + self.update_available_models() + self.update_states() + self.update_loop() + + + + + # -Widget Methods- + def create_widgets(self): + """Create window widgets""" + self.title_Label = tk.Label(master=self, bg='#0e0e0f', + image=self.logo_img, compound=tk.TOP) + self.filePaths_Frame = ttk.Frame(master=self) + self.fill_filePaths_Frame() + + self.options_Frame = ttk.Frame(master=self) + self.fill_options_Frame() + + self.conversion_Button = ttk.Button(master=self, + text='Start Processing', + command=self.start_conversion) + self.stop_Button = ttk.Button(master=self, + image=self.stop_img, + command=self.restart) + self.help_Button = ttk.Button(master=self, + image=self.help_img, + command=self.help) + + #ttk.Button(win, text= "Open", command= open_popup).pack() + + self.efile_e_Button = ttk.Button(master=self, + image=self.efile_img, + command=self.open_exportPath_filedialog) + + self.efile_i_Button = ttk.Button(master=self, + image=self.efile_img, + command=self.open_inputPath_filedialog) + + self.progressbar = ttk.Progressbar(master=self, variable=self.progress_var) + + self.command_Text = ThreadSafeConsole(master=self, + background='#0e0e0f',fg='#898b8e', font=('Century Gothic', 11), + borderwidth=0,) + + self.command_Text.write(f'Ultimate Vocal Remover [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + + + def configure_widgets(self): + """Change widget styling and appearance""" + + #ttk.Style().configure('TCheckbutton', background='#0e0e0f', + # font=self.font, foreground='#d4d4d4') + #ttk.Style().configure('TRadiobutton', background='#0e0e0f', + # font=("Century Gothic", "8", "bold"), foreground='#d4d4d4') + #ttk.Style().configure('T', font=self.font, foreground='#d4d4d4') + + #s = ttk.Style() + #s.configure('TButton', background='blue', foreground='black', font=('Century Gothic', '9', 'bold'), relief="groove") + + + def bind_widgets(self): + """Bind widgets to the drag & drop mechanic""" + self.filePaths_musicFile_Button.drop_target_register(DND_FILES) + self.filePaths_musicFile_Entry.drop_target_register(DND_FILES) + self.filePaths_saveTo_Button.drop_target_register(DND_FILES) + self.filePaths_saveTo_Entry.drop_target_register(DND_FILES) + self.filePaths_musicFile_Button.dnd_bind('<>', + lambda e: drop(e, accept_mode='files')) + self.filePaths_musicFile_Entry.dnd_bind('<>', + lambda e: drop(e, accept_mode='files')) + self.filePaths_saveTo_Button.dnd_bind('<>', + lambda e: drop(e, accept_mode='folder')) + self.filePaths_saveTo_Entry.dnd_bind('<>', + lambda e: drop(e, accept_mode='folder')) + + def place_widgets(self): + """Place main widgets""" + self.title_Label.place(x=-2, y=-2) + self.filePaths_Frame.place(x=10, y=155, width=-20, height=self.FILEPATHS_HEIGHT, + relx=0, rely=0, relwidth=1, relheight=0) + self.options_Frame.place(x=10, y=250, width=-50, height=self.OPTIONS_HEIGHT, + relx=0, rely=0, relwidth=1, relheight=0) + self.conversion_Button.place(x=50, y=self.IMAGE_HEIGHT + self.FILEPATHS_HEIGHT + self.OPTIONS_HEIGHT + self.PADDING*2, width=-60 - 40, height=self.CONVERSIONBUTTON_HEIGHT, + relx=0, rely=0, relwidth=1, relheight=0) + self.efile_e_Button.place(x=-45, y=200, width=35, height=30, + relx=1, rely=0, relwidth=0, relheight=0) + self.efile_i_Button.place(x=-45, y=160, width=35, height=30, + relx=1, rely=0, relwidth=0, relheight=0) + + self.stop_Button.place(x=-10 - 35, y=self.IMAGE_HEIGHT + self.FILEPATHS_HEIGHT + self.OPTIONS_HEIGHT + self.PADDING*2, width=35, height=self.CONVERSIONBUTTON_HEIGHT, + relx=1, rely=0, relwidth=0, relheight=0) + self.help_Button.place(x=-10 - 600, y=self.IMAGE_HEIGHT + self.FILEPATHS_HEIGHT + self.OPTIONS_HEIGHT + self.PADDING*2, width=35, height=self.CONVERSIONBUTTON_HEIGHT, + relx=1, rely=0, relwidth=0, relheight=0) + self.command_Text.place(x=25, y=self.IMAGE_HEIGHT + self.FILEPATHS_HEIGHT + self.OPTIONS_HEIGHT + self.CONVERSIONBUTTON_HEIGHT + self.PADDING*3, width=-30, height=self.COMMAND_HEIGHT, + relx=0, rely=0, relwidth=1, relheight=0) + self.progressbar.place(x=25, y=self.IMAGE_HEIGHT + self.FILEPATHS_HEIGHT + self.OPTIONS_HEIGHT + self.CONVERSIONBUTTON_HEIGHT + self.COMMAND_HEIGHT + self.PADDING*4, width=-50, height=self.PROGRESS_HEIGHT, + relx=0, rely=0, relwidth=1, relheight=0) + + def fill_filePaths_Frame(self): + """Fill Frame with neccessary widgets""" + # -Create Widgets- + # Save To Option + # Select Music Files Option + + # Save To Option + self.filePaths_saveTo_Button = ttk.Button(master=self.filePaths_Frame, + text='Select output', + command=self.open_export_filedialog) + self.filePaths_saveTo_Entry = ttk.Entry(master=self.filePaths_Frame, + + textvariable=self.exportPath_var, + state=tk.DISABLED + ) + # Select Music Files Option + self.filePaths_musicFile_Button = ttk.Button(master=self.filePaths_Frame, + text='Select input', + command=self.open_file_filedialog) + self.filePaths_musicFile_Entry = ttk.Entry(master=self.filePaths_Frame, + textvariable=self.inputPathsEntry_var, + state=tk.DISABLED + ) + + + # -Place Widgets- + + # Select Music Files Option + self.filePaths_musicFile_Button.place(x=0, y=5, width=0, height=-10, + relx=0, rely=0, relwidth=0.3, relheight=0.5) + self.filePaths_musicFile_Entry.place(x=10, y=2.5, width=-50, height=-5, + relx=0.3, rely=0, relwidth=0.7, relheight=0.5) + + # Save To Option + self.filePaths_saveTo_Button.place(x=0, y=5, width=0, height=-10, + relx=0, rely=0.5, relwidth=0.3, relheight=0.5) + self.filePaths_saveTo_Entry.place(x=10, y=2.5, width=-50, height=-5, + relx=0.3, rely=0.5, relwidth=0.7, relheight=0.5) + + + def fill_options_Frame(self): + """Fill Frame with neccessary widgets""" + # -Create Widgets- + + + # Save as wav + self.options_wav_Radiobutton = ttk.Radiobutton(master=self.options_Frame, + text='WAV', + variable=self.saveFormat_var, + value='Wav' + ) + + # Save as flac + self.options_flac_Radiobutton = ttk.Radiobutton(master=self.options_Frame, + text='FLAC', + variable=self.saveFormat_var, + value='Flac' + ) + + # Save as mp3 + self.options_mpThree_Radiobutton = ttk.Radiobutton(master=self.options_Frame, + text='MP3', + variable=self.saveFormat_var, + value='Mp3', + ) + + # -Column 1- + + # Choose Conversion Method + self.options_aiModel_Label = tk.Label(master=self.options_Frame, + text='Choose Process Method', anchor=tk.CENTER, + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_aiModel_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.aiModel_var, + None, 'VR Architecture', 'MDX-Net', 'Ensemble Mode') + # Choose Instrumental Model + self.options_instrumentalModel_Label = tk.Label(master=self.options_Frame, + text='Choose Main Model', + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_instrumentalModel_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.instrumentalModel_var) + # Choose MDX-Net Model + self.options_mdxnetModel_Label = tk.Label(master=self.options_Frame, + text='Choose MDX-Net Model', anchor=tk.CENTER, + background='#0e0e0f', font=self.font, foreground='#13a4c9') + + self.options_mdxnetModel_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.mdxnetModel_var, + None, 'UVR-MDX-NET 1', 'UVR-MDX-NET 2', 'UVR-MDX-NET 3', 'UVR-MDX-NET Karaoke') + # Ensemble Mode + self.options_ensChoose_Label = tk.Label(master=self.options_Frame, + text='Choose Ensemble', anchor=tk.CENTER, + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_ensChoose_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.ensChoose_var, + None, 'MDX-Net/VR Ensemble', 'HP Models', 'Vocal Models', 'HP2 Models', 'All HP/HP2 Models', 'User Ensemble') + + # Choose Agorithim + self.options_algo_Label = tk.Label(master=self.options_Frame, + text='Choose Algorithm', anchor=tk.CENTER, + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_algo_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.algo_var, + None, 'Vocals (Max Spec)', 'Instrumentals (Min Spec)')#, 'Invert (Normal)', 'Invert (Spectral)') + + + # -Column 2- + + # WINDOW SIZE + self.options_winSize_Label = tk.Label(master=self.options_Frame, + text='Window Size', anchor=tk.CENTER, + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_winSize_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.winSize_var, + None, '320', '512','1024') + # MDX-chunks + self.options_chunks_Label = tk.Label(master=self.options_Frame, + text='Chunks', + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_chunks_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.chunks_var, + None, 'Auto', '1', '5', '10', '15', '20', + '25', '30', '35', '40', '45', '50', + '55', '60', '65', '70', '75', '80', + '85', '90', '95', 'Full') + + #Checkboxes + # GPU Selection + self.options_gpu_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='GPU Conversion', + variable=self.gpuConversion_var, + ) + + # Vocal Only + self.options_voc_only_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Save Vocals Only', + variable=self.voc_only_var, + ) + # Instrumental Only + self.options_inst_only_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Save Instrumental Only', + variable=self.inst_only_var, + ) + # TTA + self.options_tta_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='TTA', + variable=self.tta_var, + ) + + # MDX-Auto-Chunk + self.options_non_red_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Save Noisey Vocal', + variable=self.non_red_var, + ) + + # Postprocessing + self.options_post_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Post-Process', + variable=self.postprocessing_var, + ) + + # -Column 3- + + # AGG + self.options_agg_Label = tk.Label(master=self.options_Frame, + text='Aggression Setting', + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_agg_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.agg_var, + None, '1', '2', '3', '4', '5', + '6', '7', '8', '9', '10', '11', + '12', '13', '14', '15', '16', '17', + '18', '19', '20') + + # MDX-noisereduc_s + self.options_noisereduc_s_Label = tk.Label(master=self.options_Frame, + text='Noise Reduction', + background='#0e0e0f', font=self.font, foreground='#13a4c9') + self.options_noisereduc_s_Optionmenu = ttk.OptionMenu(self.options_Frame, + self.noisereduc_s_var, + None, 'None', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '10', '11', + '12', '13', '14', '15', '16', '17', + '18', '19', '20') + + + # Save Image + self.options_image_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Output Image', + variable=self.outputImage_var, + ) + + # MDX-Enable Demucs Model + self.options_demucsmodel_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Demucs Model', + variable=self.demucsmodel_var, + ) + + # MDX-Noise Reduction + self.options_noisereduc_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Noise Reduction', + variable=self.noisereduc_var, + ) + + # Ensemble Save Ensemble Outputs + self.options_save_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Save All Outputs', + variable=self.save_var, + ) + + # Model Test Mode + self.options_modelFolder_Checkbutton = ttk.Checkbutton(master=self.options_Frame, + text='Model Test Mode', + variable=self.modelFolder_var, + ) + + # -Place Widgets- + + # -Column 0- + + # Save as + self.options_wav_Radiobutton.place(x=400, y=-5, width=0, height=6, + relx=0, rely=0/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_flac_Radiobutton.place(x=271, y=-5, width=0, height=6, + relx=1/3, rely=0/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_mpThree_Radiobutton.place(x=143, y=-5, width=0, height=6, + relx=2/3, rely=0/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + # -Column 1- + + # Choose Conversion Method + self.options_aiModel_Label.place(x=0, y=0, width=0, height=-10, + relx=0, rely=2/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_aiModel_Optionmenu.place(x=0, y=-2, width=0, height=7, + relx=0, rely=3/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # Choose Main Model + self.options_instrumentalModel_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_instrumentalModel_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # Choose MDX-Net Model + self.options_mdxnetModel_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_mdxnetModel_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # Choose Ensemble + self.options_ensChoose_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_ensChoose_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + + # Choose Algorithm + self.options_algo_Label.place(x=20, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_algo_Optionmenu.place(x=12, y=-2, width=0, height=7, + relx=1/3, rely=3/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + + # -Column 2- + + # WINDOW + self.options_winSize_Label.place(x=13, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_winSize_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=1/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #---MDX-Net Specific--- + # MDX-chunks + self.options_chunks_Label.place(x=12, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_chunks_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=1/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Checkboxes + + #GPU Conversion + self.options_gpu_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Vocals Only + self.options_voc_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Instrumental Only + self.options_inst_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + # TTA + self.options_tta_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # MDX-Keep Non_Reduced Vocal + self.options_non_red_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + # -Column 3- + + # AGG + self.options_agg_Label.place(x=15, y=0, width=0, height=-10, + relx=2/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_agg_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=2/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # MDX-noisereduc_s + self.options_noisereduc_s_Label.place(x=15, y=0, width=0, height=-10, + relx=2/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_noisereduc_s_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=2/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Checkboxes + #---MDX-Net Specific--- + # MDX-demucs Model + self.options_demucsmodel_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + #---VR Architecture Specific--- + #Post-Process + self.options_post_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Save Image + # self.options_image_Checkbutton.place(x=35, y=21, width=0, height=5, + # relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #---Ensemble Specific--- + #Ensemble Save Outputs + self.options_save_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #---MDX-Net & VR Architecture Specific--- + #Model Test Mode + self.options_modelFolder_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + # Change States + self.aiModel_var.trace_add('write', + lambda *args: self.deselect_models()) + self.ensChoose_var.trace_add('write', + lambda *args: self.update_states()) + + self.inst_only_var.trace_add('write', + lambda *args: self.update_states()) + + self.voc_only_var.trace_add('write', + lambda *args: self.update_states()) + self.noisereduc_s_var.trace_add('write', + lambda *args: self.update_states()) + self.non_red_var.trace_add('write', + lambda *args: self.update_states()) + + # Opening filedialogs + def open_file_filedialog(self): + """Make user select music files""" + if self.lastDir is not None: + if not os.path.isdir(self.lastDir): + self.lastDir = None + + paths = tk.filedialog.askopenfilenames( + parent=self, + title=f'Select Music Files', + initialfile='', + initialdir=self.lastDir, + ) + if paths: # Path selected + self.inputPaths = paths + self.update_inputPaths() + self.lastDir = os.path.dirname(paths[0]) + + def open_export_filedialog(self): + """Make user select a folder to export the converted files in""" + path = tk.filedialog.askdirectory( + parent=self, + title=f'Select Folder',) + if path: # Path selected + self.exportPath_var.set(path) + + def open_exportPath_filedialog(self): + filename = self.exportPath_var.get() + + if sys.platform == "win32": + os.startfile(filename) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, filename]) + + def open_inputPath_filedialog(self): + """Open Input Directory""" + + filename = self.lastDir + + if sys.platform == "win32": + os.startfile(filename) + + def start_conversion(self): + """ + Start the conversion for all the given mp3 and wav files + """ + + # -Get all variables- + export_path = self.exportPath_var.get() + input_paths = self.inputPaths + instrumentalModel_path = self.instrumentalLabel_to_path[self.instrumentalModel_var.get()] # nopep8 + # mdxnetModel_path = self.mdxnetLabel_to_path[self.mdxnetModel_var.get()] + # Get constants + instrumental = self.instrumentalModel_var.get() + try: + if [bool(instrumental)].count(True) == 2: #CHECKTHIS + window_size = DEFAULT_DATA['window_size'] + agg = DEFAULT_DATA['agg'] + chunks = DEFAULT_DATA['chunks'] + noisereduc_s = DEFAULT_DATA['noisereduc_s'] + mixing = DEFAULT_DATA['mixing'] + else: + window_size = int(self.winSize_var.get()) + agg = int(self.agg_var.get()) + chunks = str(self.chunks_var.get()) + noisereduc_s = str(self.noisereduc_s_var.get()) + mixing = str(self.mixing_var.get()) + ensChoose = str(self.ensChoose_var.get()) + mdxnetModel = str(self.mdxnetModel_var.get()) + + except SyntaxError: # Non integer was put in entry box + 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 + + # -Check for invalid inputs- + 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 self.aiModel_var.get() == 'VR Architecture': + if not os.path.isfile(instrumentalModel_path): + tk.messagebox.showwarning(master=self, + title='Invalid Main Model File', + message='You have selected an invalid main model file!\nPlease make sure that your model file still exists!') + return + + if not os.path.isdir(export_path): + tk.messagebox.showwarning(master=self, + title='Invalid Export Directory', + message='You have selected an invalid export directory!\nPlease make sure that your directory still exists!') + return + + if self.aiModel_var.get() == 'VR Architecture': + inference = inference_v5 + elif self.aiModel_var.get() == 'Ensemble Mode': + inference = inference_v5_ensemble + elif self.aiModel_var.get() == 'MDX-Net': + inference = inference_MDX + else: + raise TypeError('This error should not occur.') + + # -Run the algorithm- + threading.Thread(target=inference.main, + kwargs={ + # Paths + 'input_paths': input_paths, + 'export_path': export_path, + 'saveFormat': self.saveFormat_var.get(), + # Processing Options + 'gpu': 0 if self.gpuConversion_var.get() else -1, + 'postprocess': self.postprocessing_var.get(), + 'tta': self.tta_var.get(), + 'save': self.save_var.get(), + 'output_image': self.outputImage_var.get(), + 'algo': self.algo_var.get(), + # Models + 'instrumentalModel': instrumentalModel_path, + 'vocalModel': '', # Always not needed + 'useModel': 'instrumental', # Always instrumental + # Model Folder + 'modelFolder': self.modelFolder_var.get(), + # Constants + 'window_size': window_size, + 'agg': agg, + 'break': False, + 'ensChoose': ensChoose, + 'mdxnetModel': mdxnetModel, + # Other Variables (Tkinter) + 'window': self, + 'text_widget': self.command_Text, + 'button_widget': self.conversion_Button, + 'inst_menu': self.options_instrumentalModel_Optionmenu, + 'progress_var': self.progress_var, + # MDX-Net Specific + 'demucsmodel': self.demucsmodel_var.get(), + 'non_red': self.non_red_var.get(), + 'noise_reduc': self.noisereduc_var.get(), + 'voc_only': self.voc_only_var.get(), + 'inst_only': self.inst_only_var.get(), + 'chunks': chunks, + 'noisereduc_s': noisereduc_s, + 'mixing': mixing, + }, + daemon=True + ).start() + + # Models + def update_inputPaths(self): + """Update the music file entry""" + if self.inputPaths: + # Non-empty Selection + text = '; '.join(self.inputPaths) + else: + # Empty Selection + text = '' + self.inputPathsEntry_var.set(text) + + def update_loop(self): + """Update the dropdown menu""" + self.update_available_models() + + self.after(3000, self.update_loop) + + def update_available_models(self): + """ + Loop through every model (.pth) in the models directory + and add to the select your model list + """ + temp_instrumentalModels_dir = os.path.join(instrumentalModels_dir, 'Main_Models') # nopep8 + + # Main models + new_InstrumentalModels = os.listdir(temp_instrumentalModels_dir) + if new_InstrumentalModels != self.lastInstrumentalModels: + self.instrumentalLabel_to_path.clear() + self.options_instrumentalModel_Optionmenu['menu'].delete(0, 'end') + for file_name in new_InstrumentalModels: + if file_name.endswith('.pth'): + # Add Radiobutton to the Options Menu + self.options_instrumentalModel_Optionmenu['menu'].add_radiobutton(label=file_name, + command=tk._setit(self.instrumentalModel_var, file_name)) + # Link the files name to its absolute path + self.instrumentalLabel_to_path[file_name] = os.path.join(temp_instrumentalModels_dir, file_name) # nopep8 + self.lastInstrumentalModels = new_InstrumentalModels + + def update_states(self): + """ + Vary the states for all widgets based + on certain selections + """ + + if self.aiModel_var.get() == 'MDX-Net': + # Place Widgets + + # Choose MDX-Net Model + self.options_mdxnetModel_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_mdxnetModel_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # MDX-chunks + self.options_chunks_Label.place(x=12, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_chunks_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=1/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # MDX-noisereduc_s + self.options_noisereduc_s_Label.place(x=15, y=0, width=0, height=-10, + relx=2/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_noisereduc_s_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=2/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #GPU Conversion + self.options_gpu_Checkbutton.configure(state=tk.NORMAL) + self.options_gpu_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Vocals Only + self.options_voc_only_Checkbutton.configure(state=tk.NORMAL) + self.options_voc_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Instrumental Only + self.options_inst_only_Checkbutton.configure(state=tk.NORMAL) + self.options_inst_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # MDX-demucs Model + self.options_demucsmodel_Checkbutton.configure(state=tk.NORMAL) + self.options_demucsmodel_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + # MDX-Keep Non_Reduced Vocal + self.options_non_red_Checkbutton.configure(state=tk.NORMAL) + self.options_non_red_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Model Test Mode + self.options_modelFolder_Checkbutton.configure(state=tk.NORMAL) + self.options_modelFolder_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + + # Forget widgets + self.options_ensChoose_Label.place_forget() + self.options_ensChoose_Optionmenu.place_forget() + self.options_instrumentalModel_Label.place_forget() + self.options_instrumentalModel_Optionmenu.place_forget() + self.options_save_Checkbutton.configure(state=tk.DISABLED) + self.options_save_Checkbutton.place_forget() + self.options_post_Checkbutton.configure(state=tk.DISABLED) + self.options_post_Checkbutton.place_forget() + self.options_tta_Checkbutton.configure(state=tk.DISABLED) + self.options_tta_Checkbutton.place_forget() + # self.options_image_Checkbutton.configure(state=tk.DISABLED) + # self.options_image_Checkbutton.place_forget() + self.options_winSize_Label.place_forget() + self.options_winSize_Optionmenu.place_forget() + self.options_agg_Label.place_forget() + self.options_agg_Optionmenu.place_forget() + self.options_algo_Label.place_forget() + self.options_algo_Optionmenu.place_forget() + + + elif self.aiModel_var.get() == 'VR Architecture': + #Keep for Ensemble & VR Architecture Mode + # Choose Main Model + self.options_instrumentalModel_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_instrumentalModel_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # WINDOW + self.options_winSize_Label.place(x=13, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_winSize_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=1/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # AGG + self.options_agg_Label.place(x=15, y=0, width=0, height=-10, + relx=2/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_agg_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=2/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #GPU Conversion + self.options_gpu_Checkbutton.configure(state=tk.NORMAL) + self.options_gpu_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Vocals Only + self.options_voc_only_Checkbutton.configure(state=tk.NORMAL) + self.options_voc_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Instrumental Only + self.options_inst_only_Checkbutton.configure(state=tk.NORMAL) + self.options_inst_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # TTA + self.options_tta_Checkbutton.configure(state=tk.NORMAL) + self.options_tta_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Post-Process + self.options_post_Checkbutton.configure(state=tk.NORMAL) + self.options_post_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Save Image + # self.options_image_Checkbutton.configure(state=tk.NORMAL) + # self.options_image_Checkbutton.place(x=35, y=21, width=0, height=5, + # relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Model Test Mode + self.options_modelFolder_Checkbutton.configure(state=tk.NORMAL) + self.options_modelFolder_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Forget Widgets + self.options_ensChoose_Label.place_forget() + self.options_ensChoose_Optionmenu.place_forget() + self.options_chunks_Label.place_forget() + self.options_chunks_Optionmenu.place_forget() + self.options_noisereduc_s_Label.place_forget() + self.options_noisereduc_s_Optionmenu.place_forget() + self.options_mdxnetModel_Label.place_forget() + self.options_mdxnetModel_Optionmenu.place_forget() + self.options_algo_Label.place_forget() + self.options_algo_Optionmenu.place_forget() + self.options_save_Checkbutton.configure(state=tk.DISABLED) + self.options_save_Checkbutton.place_forget() + self.options_non_red_Checkbutton.configure(state=tk.DISABLED) + self.options_non_red_Checkbutton.place_forget() + self.options_noisereduc_Checkbutton.configure(state=tk.DISABLED) + self.options_noisereduc_Checkbutton.place_forget() + self.options_demucsmodel_Checkbutton.configure(state=tk.DISABLED) + self.options_demucsmodel_Checkbutton.place_forget() + self.options_non_red_Checkbutton.configure(state=tk.DISABLED) + self.options_non_red_Checkbutton.place_forget() + + elif self.aiModel_var.get() == 'Ensemble Mode': + if self.ensChoose_var.get() == 'User Ensemble': + # Choose Algorithm + self.options_algo_Label.place(x=20, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_algo_Optionmenu.place(x=12, y=-2, width=0, height=7, + relx=1/3, rely=3/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # Choose Ensemble + self.options_ensChoose_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_ensChoose_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # Forget Widgets + self.options_save_Checkbutton.configure(state=tk.DISABLED) + self.options_save_Checkbutton.place_forget() + self.options_post_Checkbutton.configure(state=tk.DISABLED) + self.options_post_Checkbutton.place_forget() + self.options_tta_Checkbutton.configure(state=tk.DISABLED) + self.options_tta_Checkbutton.place_forget() + self.options_modelFolder_Checkbutton.configure(state=tk.DISABLED) + self.options_modelFolder_Checkbutton.place_forget() + # self.options_image_Checkbutton.configure(state=tk.DISABLED) + # self.options_image_Checkbutton.place_forget() + self.options_gpu_Checkbutton.configure(state=tk.DISABLED) + self.options_gpu_Checkbutton.place_forget() + self.options_voc_only_Checkbutton.configure(state=tk.DISABLED) + self.options_voc_only_Checkbutton.place_forget() + self.options_inst_only_Checkbutton.configure(state=tk.DISABLED) + self.options_inst_only_Checkbutton.place_forget() + self.options_demucsmodel_Checkbutton.configure(state=tk.DISABLED) + self.options_demucsmodel_Checkbutton.place_forget() + self.options_noisereduc_Checkbutton.configure(state=tk.DISABLED) + self.options_noisereduc_Checkbutton.place_forget() + self.options_non_red_Checkbutton.configure(state=tk.DISABLED) + self.options_non_red_Checkbutton.place_forget() + self.options_chunks_Label.place_forget() + self.options_chunks_Optionmenu.place_forget() + self.options_noisereduc_s_Label.place_forget() + self.options_noisereduc_s_Optionmenu.place_forget() + self.options_mdxnetModel_Label.place_forget() + self.options_mdxnetModel_Optionmenu.place_forget() + self.options_winSize_Label.place_forget() + self.options_winSize_Optionmenu.place_forget() + self.options_agg_Label.place_forget() + self.options_agg_Optionmenu.place_forget() + + elif self.ensChoose_var.get() == 'MDX-Net/VR Ensemble': + # Choose Ensemble + self.options_ensChoose_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_ensChoose_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # MDX-chunks + self.options_chunks_Label.place(x=12, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_chunks_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=1/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # MDX-noisereduc_s + self.options_noisereduc_s_Label.place(x=15, y=0, width=0, height=-10, + relx=2/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_noisereduc_s_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=2/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # WINDOW + self.options_winSize_Label.place(x=13, y=-7, width=0, height=-10, + relx=1/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_winSize_Optionmenu.place(x=71, y=-5, width=-118, height=7, + relx=1/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # AGG + self.options_agg_Label.place(x=15, y=-7, width=0, height=-10, + relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_agg_Optionmenu.place(x=71, y=-5, width=-118, height=7, + relx=2/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #GPU Conversion + self.options_gpu_Checkbutton.configure(state=tk.NORMAL) + self.options_gpu_Checkbutton.place(x=35, y=3, width=0, height=5, + relx=1/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Vocals Only + self.options_voc_only_Checkbutton.configure(state=tk.NORMAL) + self.options_voc_only_Checkbutton.place(x=35, y=3, width=0, height=5, + relx=1/3, rely=8/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Instrumental Only + self.options_inst_only_Checkbutton.configure(state=tk.NORMAL) + self.options_inst_only_Checkbutton.place(x=35, y=3, width=0, height=5, + relx=1/3, rely=9/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # MDX-demucs Model + self.options_demucsmodel_Checkbutton.configure(state=tk.NORMAL) + self.options_demucsmodel_Checkbutton.place(x=35, y=3, width=0, height=5, + relx=2/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # TTA + self.options_tta_Checkbutton.configure(state=tk.NORMAL) + self.options_tta_Checkbutton.place(x=35, y=3, width=0, height=5, + relx=2/3, rely=8/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Ensemble Save Outputs + self.options_save_Checkbutton.configure(state=tk.NORMAL) + self.options_save_Checkbutton.place(x=35, y=3, width=0, height=5, + relx=2/3, rely=9/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # Forget Widgets + self.options_post_Checkbutton.configure(state=tk.DISABLED) + self.options_post_Checkbutton.place_forget() + self.options_modelFolder_Checkbutton.configure(state=tk.DISABLED) + self.options_modelFolder_Checkbutton.place_forget() + # self.options_image_Checkbutton.configure(state=tk.DISABLED) + # self.options_image_Checkbutton.place_forget() + self.options_noisereduc_Checkbutton.configure(state=tk.DISABLED) + self.options_noisereduc_Checkbutton.place_forget() + self.options_non_red_Checkbutton.configure(state=tk.DISABLED) + self.options_non_red_Checkbutton.place_forget() + self.options_algo_Label.place_forget() + self.options_algo_Optionmenu.place_forget() + else: + # Choose Ensemble + self.options_ensChoose_Label.place(x=0, y=19, width=0, height=-10, + relx=0, rely=6/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + self.options_ensChoose_Optionmenu.place(x=0, y=19, width=0, height=7, + relx=0, rely=7/self.COL1_ROWS, relwidth=1/3, relheight=1/self.COL1_ROWS) + # WINDOW + self.options_winSize_Label.place(x=13, y=0, width=0, height=-10, + relx=1/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_winSize_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=1/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # AGG + self.options_agg_Label.place(x=15, y=0, width=0, height=-10, + relx=2/3, rely=2/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + self.options_agg_Optionmenu.place(x=71, y=-2, width=-118, height=7, + relx=2/3, rely=3/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #GPU Conversion + self.options_gpu_Checkbutton.configure(state=tk.NORMAL) + self.options_gpu_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Vocals Only + self.options_voc_only_Checkbutton.configure(state=tk.NORMAL) + self.options_voc_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Instrumental Only + self.options_inst_only_Checkbutton.configure(state=tk.NORMAL) + self.options_inst_only_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=1/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + # TTA + self.options_tta_Checkbutton.configure(state=tk.NORMAL) + self.options_tta_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Post-Process + self.options_post_Checkbutton.configure(state=tk.NORMAL) + self.options_post_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=6/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Save Image + # self.options_image_Checkbutton.configure(state=tk.NORMAL) + # self.options_image_Checkbutton.place(x=35, y=21, width=0, height=5, + # relx=2/3, rely=5/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Ensemble Save Outputs + self.options_save_Checkbutton.configure(state=tk.NORMAL) + self.options_save_Checkbutton.place(x=35, y=21, width=0, height=5, + relx=2/3, rely=7/self.COL2_ROWS, relwidth=1/3, relheight=1/self.COL2_ROWS) + #Forget Widgets + self.options_algo_Label.place_forget() + self.options_algo_Optionmenu.place_forget() + self.options_instrumentalModel_Label.place_forget() + self.options_instrumentalModel_Optionmenu.place_forget() + self.options_chunks_Label.place_forget() + self.options_chunks_Optionmenu.place_forget() + self.options_noisereduc_s_Label.place_forget() + self.options_noisereduc_s_Optionmenu.place_forget() + self.options_mdxnetModel_Label.place_forget() + self.options_mdxnetModel_Optionmenu.place_forget() + self.options_modelFolder_Checkbutton.place_forget() + self.options_modelFolder_Checkbutton.configure(state=tk.DISABLED) + self.options_noisereduc_Checkbutton.place_forget() + self.options_noisereduc_Checkbutton.configure(state=tk.DISABLED) + self.options_demucsmodel_Checkbutton.place_forget() + self.options_demucsmodel_Checkbutton.configure(state=tk.DISABLED) + self.options_non_red_Checkbutton.place_forget() + self.options_non_red_Checkbutton.configure(state=tk.DISABLED) + + + if self.inst_only_var.get() == True: + self.options_voc_only_Checkbutton.configure(state=tk.DISABLED) + self.voc_only_var.set(False) + self.non_red_var.set(False) + elif self.inst_only_var.get() == False: + self.options_non_red_Checkbutton.configure(state=tk.NORMAL) + self.options_voc_only_Checkbutton.configure(state=tk.NORMAL) + + if self.voc_only_var.get() == True: + self.options_inst_only_Checkbutton.configure(state=tk.DISABLED) + self.inst_only_var.set(False) + elif self.voc_only_var.get() == False: + self.options_inst_only_Checkbutton.configure(state=tk.NORMAL) + + if self.noisereduc_s_var.get() == 'None': + self.options_non_red_Checkbutton.configure(state=tk.DISABLED) + self.non_red_var.set(False) + if not self.noisereduc_s_var.get() == 'None': + self.options_non_red_Checkbutton.configure(state=tk.NORMAL) + + + self.update_inputPaths() + + def deselect_models(self): + """ + Run this method on version change + """ + if self.aiModel_var.get() == self.last_aiModel: + return + else: + self.last_aiModel = self.aiModel_var.get() + + self.instrumentalModel_var.set('') + self.ensChoose_var.set('MDX-Net/VR Ensemble') + self.mdxnetModel_var.set('UVR-MDX-NET 1') + + self.winSize_var.set(DEFAULT_DATA['window_size']) + self.agg_var.set(DEFAULT_DATA['agg']) + self.modelFolder_var.set(DEFAULT_DATA['modelFolder']) + + + self.update_available_models() + self.update_states() + + def restart(self): + """ + Restart the application after asking for confirmation + """ + confirm = tk.messagebox.askyesno(title='Restart Confirmation', + message='This will restart the application and halt any running processes. Your current settings will be saved. \n\n Are you sure you wish to continue?') + + if confirm: + self.save_values() + + subprocess.Popen(f'UVR_Launcher.exe') + exit() + else: + pass + + def help(self): + """ + Open Help Guide + """ + top= Toplevel(self) + top.geometry("1080x920") + top.title("UVR Help Guide") + + top.resizable(False, False) # This code helps to disable windows from resizing + + window_height = 920 + window_width = 1080 + + screen_width = top.winfo_screenwidth() + screen_height = top.winfo_screenheight() + + x_cordinate = int((screen_width/2) - (window_width/2)) + y_cordinate = int((screen_height/2) - (window_height/2)) + + top.geometry("{}x{}+{}+{}".format(window_width, window_height, x_cordinate, y_cordinate)) + + # change title bar icon + top.iconbitmap('img\\UVR-Icon-v2.ico') + + tabControl = ttk.Notebook(top) + + tab1 = ttk.Frame(tabControl) + tab2 = ttk.Frame(tabControl) + tab3 = ttk.Frame(tabControl) + tab4 = ttk.Frame(tabControl) + tab5 = ttk.Frame(tabControl) + tab6 = ttk.Frame(tabControl) + tab7 = ttk.Frame(tabControl) + tab8 = ttk.Frame(tabControl) + tab9 = ttk.Frame(tabControl) + + tabControl.add(tab1, text ='General Options') + tabControl.add(tab2, text ='VR Architecture Options') + tabControl.add(tab3, text ='MDX-Net Options') + tabControl.add(tab4, text ='Ensemble Mode') + tabControl.add(tab5, text ='User Ensemble') + tabControl.add(tab6, text ='More Info') + tabControl.add(tab7, text ='Credits') + tabControl.add(tab8, text ='Updates') + tabControl.add(tab9, text ='Error Log') + + tabControl.pack(expand = 1, fill ="both") + + #Configure the row/col of our frame and root window to be resizable and fill all available space + tab6.grid_rowconfigure(0, weight=1) + tab6.grid_columnconfigure(0, weight=1) + + tab7.grid_rowconfigure(0, weight=1) + tab7.grid_columnconfigure(0, weight=1) + + tab8.grid_rowconfigure(0, weight=1) + tab8.grid_columnconfigure(0, weight=1) + + tab9.grid_rowconfigure(0, weight=1) + tab9.grid_columnconfigure(0, weight=1) + + ttk.Label(tab1, image=self.gen_opt_img).grid(column = 0, + row = 0, + padx = 30, + pady = 30) + + ttk.Label(tab2, image=self.vr_opt_img).grid(column = 0, + row = 0, + padx = 30, + pady = 30) + + ttk.Label(tab3, image=self.mdx_opt_img).grid(column = 0, + row = 0, + padx = 30, + pady = 30) + + ttk.Label(tab4, image=self.ense_opt_img).grid(column = 0, + row = 0, + padx = 30, + pady = 30) + + ttk.Label(tab5, image=self.user_ens_opt_img).grid(column = 0, + row = 0, + padx = 30, + pady = 30) + + #frame0 + frame0=Frame(tab6,highlightbackground='red',highlightthicknes=0) + frame0.grid(row=0,column=0,padx=0,pady=30) + + l0=Label(frame0,text="Notes",font=("Century Gothic", "16", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=1,column=0,padx=20,pady=15) + + l0=Label(frame0,text="UVR is 100% free and open-source but MIT licensed.\nAll the models provided as part of UVR were trained by its core developers.\nPlease credit the core UVR developers if you choose to use any of our models or code for projects unrelated to UVR.",font=("Century Gothic", "13"), justify="center", fg="#F6F6F7") + l0.grid(row=2,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Resources",font=("Century Gothic", "16", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=3,column=0,padx=20,pady=15, sticky=N) + + link = Label(frame0, text="Ultimate Vocal Remover (Official GitHub)",font=("Century Gothic", "14", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=4,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://github.com/Anjok07/ultimatevocalremovergui")) + + l0=Label(frame0,text="You can find updates, report issues, and give us a shout via our official GitHub.",font=("Century Gothic", "13"), justify="center", fg="#F6F6F7") + l0.grid(row=5,column=0,padx=10,pady=10) + + link = Label(frame0, text="SoX - Sound eXchange",font=("Century Gothic", "14", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=6,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://sourceforge.net/projects/sox/files/sox/14.4.2/sox-14.4.2-win32.zip/download")) + + l0=Label(frame0,text="UVR relies on SoX for Noise Reduction. It's automatically included via the UVR installer but not the developer build.\nIf you are missing SoX, please download it via the link and extract the SoX archive to the following directory - lib_v5/sox",font=("Century Gothic", "13"), justify="center", fg="#F6F6F7") + l0.grid(row=7,column=0,padx=10,pady=10) + + link = Label(frame0, text="FFmpeg",font=("Century Gothic", "14", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=8,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://www.wikihow.com/Install-FFmpeg-on-Windows")) + + l0=Label(frame0,text="UVR relies on FFmpeg for processing non-wav audio files.\nIt's automatically included via the UVR installer but not the developer build.\nIf you are missing FFmpeg, please see the installation guide via the link provided.",font=("Century Gothic", "13"), justify="center", fg="#F6F6F7") + l0.grid(row=9,column=0,padx=10,pady=10) + + link = Label(frame0, text="X-Minus AI",font=("Century Gothic", "14", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=10,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://x-minus.pro/ai")) + + l0=Label(frame0,text="Many of the models provided are also on X-Minus.\nThis resource primarily benefits users without the computing resources to run the GUI or models locally.",font=("Century Gothic", "13"), justify="center", fg="#F6F6F7") + l0.grid(row=11,column=0,padx=10,pady=10) + + link = Label(frame0, text="Official UVR Patreon",font=("Century Gothic", "14", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=12,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://www.patreon.com/uvr")) + + l0=Label(frame0,text="If you wish to support and donate to this project, click the link above and become a Patreon!",font=("Century Gothic", "13"), justify="center", fg="#F6F6F7") + l0.grid(row=13,column=0,padx=10,pady=10) + + frame0=Frame(tab7,highlightbackground='red',highlightthicknes=0) + frame0.grid(row=0,column=0,padx=0,pady=30) + + #inside frame0 + + l0=Label(frame0,text="Core UVR Developers",font=("Century Gothic", "16", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=0,column=0,padx=20,pady=10, sticky=N) + + l0=Label(frame0,image=self.credits_img,font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9") + l0.grid(row=1,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Anjok07\nAufr33",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9") + l0.grid(row=2,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Special Thanks",font=("Century Gothic", "16", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=4,column=0,padx=20,pady=15) + + l0=Label(frame0,text="DilanBoskan",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9") + l0.grid(row=5,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Your contributions at the start of this project were essential to the success of UVR. Thank you!",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=6,column=0,padx=0,pady=0) + + link = Label(frame0, text="Tsurumeso",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=7,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://github.com/tsurumeso/vocal-remover")) + + l0=Label(frame0,text="Developed the original VR Architecture AI code.",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=8,column=0,padx=0,pady=0) + + link = Label(frame0, text="Kuielab & Woosung Choi",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=9,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://github.com/kuielab")) + + l0=Label(frame0,text="Developed the original MDX-Net AI code.",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=10,column=0,padx=0,pady=0) + + l0=Label(frame0,text="Bas Curtiz",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9") + l0.grid(row=11,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Designed the official UVR logo, icon, banner, splash screen, and interface.",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=12,column=0,padx=0,pady=0) + + link = Label(frame0, text="Adefossez & Demucs",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=13,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://github.com/facebookresearch/demucs")) + + l0=Label(frame0,text="Core developer of Facebook's Demucs Music Source Separation.",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=14,column=0,padx=0,pady=0) + + l0=Label(frame0,text="Audio Separation Discord Community",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9") + l0.grid(row=15,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Thank you for the support!",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=16,column=0,padx=0,pady=0) + + l0=Label(frame0,text="CC Karokee & Friends Discord Community",font=("Century Gothic", "13", "bold"), justify="center", fg="#13a4c9") + l0.grid(row=17,column=0,padx=10,pady=10) + + l0=Label(frame0,text="Thank you for the support!",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=18,column=0,padx=0,pady=0) + + frame0=Frame(tab8,highlightbackground='red',highlightthicknes=0) + frame0.grid(row=0,column=0,padx=0,pady=30) + + l0=Label(frame0,text="Update Details",font=("Century Gothic", "16", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=1,column=0,padx=20,pady=10) + + l0=Label(frame0,text="Installing Model Expansion Pack",font=("Century Gothic", "13", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=2,column=0,padx=0,pady=0) + + l0=Label(frame0,text="1. Download the model expansion pack via the provided link below.\n2. Once the download has completed, click the \"Open Models Directory\" button below.\n3. Extract the \'Main Models\' folder within the downloaded archive to the opened \"models\" directory.\n4. Without restarting the application, you will now see the new models appear under the VR Architecture model selection list.",font=("Century Gothic", "11"), justify="center", fg="#f4f4f4") + l0.grid(row=3,column=0,padx=0,pady=0) + + link = Label(frame0, text="Model Expansion Pack",font=("Century Gothic", "11", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=4,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://github.com/Anjok07/ultimatevocalremovergui/releases/tag/v5.2.0")) + + l0=Button(frame0,text='Open Models Directory',font=("Century Gothic", "11"), command=self.open_Modelfolder_filedialog, justify="left", wraplength=1000, bg="black", relief="ridge") + l0.grid(row=5,column=0,padx=0,pady=0) + + l0=Label(frame0,text="\n\n\nBackward Compatibility",font=("Century Gothic", "13", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=6,column=0,padx=0,pady=0) + + l0=Label(frame0,text="The v4 Models are fully compatible with this GUI. \n1. If you already have them on your system, click the \"Open Models Directory\" button below. \n2. Place the files with extension \".pth\" into the \"Main Models\" directory. \n3. Now they will automatically appear in the VR Architecture model selection list.\n Note: The v2 models are not compatible with this GUI.\n",font=("Century Gothic", "11"), justify="center", fg="#f4f4f4") + l0.grid(row=7,column=0,padx=0,pady=0) + + l0=Button(frame0,text='Open Models Directory',font=("Century Gothic", "11"), command=self.open_Modelfolder_filedialog, justify="left", wraplength=1000, bg="black", relief="ridge") + l0.grid(row=8,column=0,padx=0,pady=0) + + l0=Label(frame0,text="\n\n\nInstalling Future Updates",font=("Century Gothic", "13", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=9,column=0,padx=0,pady=0) + + l0=Label(frame0,text="New updates and patches for this application can be found on the official UVR Releases GitHub page (link below).\nAny new update instructions will likely require the use of the \"Open Application Directory\" button below.",font=("Century Gothic", "11"), justify="center", fg="#f4f4f4") + l0.grid(row=10,column=0,padx=0,pady=0) + + link = Label(frame0, text="UVR Releases GitHub Page",font=("Century Gothic", "11", "underline"), justify="center", fg="#13a4c9", cursor="hand2") + link.grid(row=11,column=0,padx=10,pady=10) + link.bind("", lambda e: + callback("https://github.com/Anjok07/ultimatevocalremovergui/releases")) + + l0=Button(frame0,text='Open Application Directory',font=("Century Gothic", "11"), command=self.open_appdir_filedialog, justify="left", wraplength=1000, bg="black", relief="ridge") + l0.grid(row=12,column=0,padx=0,pady=0) + + frame0=Frame(tab9,highlightbackground='red',highlightthicknes=0) + frame0.grid(row=0,column=0,padx=0,pady=30) + + l0=Label(frame0,text="Error Details",font=("Century Gothic", "16", "bold"), justify="center", fg="#f4f4f4") + l0.grid(row=1,column=0,padx=20,pady=10) + + l0=Label(frame0,text="This tab will show the raw details of the last error received.",font=("Century Gothic", "12"), justify="center", fg="#F6F6F7") + l0.grid(row=2,column=0,padx=0,pady=0) + + l0=Label(frame0,text="(Click the error console below to copy the error)\n",font=("Century Gothic", "10"), justify="center", fg="#F6F6F7") + l0.grid(row=3,column=0,padx=0,pady=0) + + with open("errorlog.txt", "r") as f: + l0=Button(frame0,text=f.read(),font=("Century Gothic", "11"), command=self.copy_clip, justify="left", wraplength=1000, fg="#FF0000", bg="black", relief="sunken") + l0.grid(row=4,column=0,padx=0,pady=0) + + l0=Label(frame0,text="",font=("Century Gothic", "10"), justify="center", fg="#F6F6F7") + l0.grid(row=5,column=0,padx=0,pady=0) + + def copy_clip(self): + copy_t = open("errorlog.txt", "r").read() + pyperclip.copy(copy_t) + + def open_Modelfolder_filedialog(self): + """Let user paste a ".pth" model to use for the vocal seperation""" + filename = 'models' + + if sys.platform == "win32": + os.startfile(filename) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, filename]) + + def open_appdir_filedialog(self): + + pathname = '.' + + print(pathname) + + if sys.platform == "win32": + os.startfile(pathname) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, filename]) + + def save_values(self): + """ + Save the data of the application + """ + # Get constants + instrumental = self.instrumentalModel_var.get() + if [bool(instrumental)].count(True) == 2: #Checkthis + window_size = DEFAULT_DATA['window_size'] + agg = DEFAULT_DATA['agg'] + chunks = DEFAULT_DATA['chunks'] + noisereduc_s = DEFAULT_DATA['noisereduc_s'] + mixing = DEFAULT_DATA['mixing'] + else: + window_size = self.winSize_var.get() + agg = self.agg_var.get() + chunks = self.chunks_var.get() + noisereduc_s = self.noisereduc_s_var.get() + mixing = self.mixing_var.get() + + # -Save Data- + save_data(data={ + 'exportPath': self.exportPath_var.get(), + 'inputPaths': self.inputPaths, + 'saveFormat': self.saveFormat_var.get(), + 'gpu': self.gpuConversion_var.get(), + 'postprocess': self.postprocessing_var.get(), + 'tta': self.tta_var.get(), + 'save': self.save_var.get(), + 'output_image': self.outputImage_var.get(), + 'window_size': window_size, + 'agg': agg, + 'useModel': 'instrumental', + 'lastDir': self.lastDir, + 'modelFolder': self.modelFolder_var.get(), + 'modelInstrumentalLabel': self.instrumentalModel_var.get(), + 'aiModel': self.aiModel_var.get(), + 'algo': self.algo_var.get(), + 'ensChoose': self.ensChoose_var.get(), + 'mdxnetModel': self.mdxnetModel_var.get(), + #MDX-Net + 'demucsmodel': self.demucsmodel_var.get(), + 'non_red': self.non_red_var.get(), + 'noise_reduc': self.noisereduc_var.get(), + 'voc_only': self.voc_only_var.get(), + 'inst_only': self.inst_only_var.get(), + 'chunks': chunks, + 'noisereduc_s': noisereduc_s, + 'mixing': mixing, + }) + + self.destroy() + +if __name__ == "__main__": + + root = MainWindow() + + root.tk.call( + 'wm', + 'iconphoto', + root._w, + tk.PhotoImage(file='img\\GUI-icon.png') + ) + + lib_v5.sv_ttk.set_theme("dark") + lib_v5.sv_ttk.use_dark_theme() # Set dark theme + + #Define a callback function + def callback(url): + webbrowser.open_new_tab(url) + + root.mainloop() diff --git a/inference_MDX.py b/inference_MDX.py new file mode 100644 index 0000000..9e4a01e --- /dev/null +++ b/inference_MDX.py @@ -0,0 +1,1024 @@ +import os +from pickle import STOP +from tracemalloc import stop +from turtle import update +import subprocess +from unittest import skip +from pathlib import Path +import os.path +from datetime import datetime +import pydub +import shutil +#MDX-Net +#---------------------------------------- +import soundfile as sf +import torch +import numpy as np +from demucs.model import Demucs +from demucs.utils import apply_model +from models import get_models, spec_effects +import onnxruntime as ort +import time +import os +from tqdm import tqdm +import warnings +import sys +import librosa +import psutil +#---------------------------------------- +from lib_v5 import spec_utils +from lib_v5.model_param_init import ModelParameters +import torch + +# Command line text parsing and widget manipulation +import tkinter as tk +import traceback # Error Message Recent Calls +import time # Timer + +class Predictor(): + def __init__(self): + pass + + def prediction_setup(self, demucs_name, + channels=64): + if data['demucsmodel']: + self.demucs = Demucs(sources=["drums", "bass", "other", "vocals"], channels=channels) + widget_text.write(base_text + 'Loading Demucs model... ') + update_progress(**progress_kwargs, + step=0.05) + self.demucs.to(device) + self.demucs.load_state_dict(torch.load(demucs_name)) + widget_text.write('Done!\n') + self.demucs.eval() + self.onnx_models = {} + c = 0 + + self.models = get_models('tdf_extra', load=False, device=cpu, stems='vocals') + widget_text.write(base_text + 'Loading ONNX model... ') + update_progress(**progress_kwargs, + step=0.1) + c+=1 + + if data['gpu'] >= 0: + if torch.cuda.is_available(): + run_type = ['CUDAExecutionProvider'] + else: + data['gpu'] = -1 + widget_text.write("\n" + base_text + "No NVIDIA GPU detected. Switching to CPU... ") + run_type = ['CPUExecutionProvider'] + else: + run_type = ['CPUExecutionProvider'] + + self.onnx_models[c] = ort.InferenceSession(os.path.join('models/MDX_Net_Models', model_set), providers=run_type) + widget_text.write('Done!\n') + + def prediction(self, m): + #mix, rate = sf.read(m) + mix, rate = librosa.load(m, mono=False, sr=44100) + if mix.ndim == 1: + mix = np.asfortranarray([mix,mix]) + mix = mix.T + sources = self.demix(mix.T) + widget_text.write(base_text + 'Inferences complete!\n') + c = -1 + + #Main Save Path + save_path = os.path.dirname(_basename) + + #Vocal Path + vocal_name = '(Vocals)' + if data['modelFolder']: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_{model_set_name}',) + vocal_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_{model_set_name}',) + vocal_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_{model_set_name}',) + else: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}',) + vocal_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}',) + vocal_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}',) + + #Instrumental Path + Instrumental_name = '(Instrumental)' + if data['modelFolder']: + Instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{Instrumental_name}_{model_set_name}',) + Instrumental_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{Instrumental_name}_{model_set_name}',) + Instrumental_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{Instrumental_name}_{model_set_name}',) + else: + Instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{Instrumental_name}',) + Instrumental_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{Instrumental_name}',) + Instrumental_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{Instrumental_name}',) + + #Non-Reduced Vocal Path + vocal_name = '(Vocals)' + if data['modelFolder']: + non_reduced_vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_{model_set_name}_No_Reduction',) + non_reduced_vocal_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_{model_set_name}_No_Reduction',) + non_reduced_vocal_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_{model_set_name}_No_Reduction',) + else: + non_reduced_vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_No_Reduction',) + non_reduced_vocal_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_No_Reduction',) + non_reduced_vocal_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name = f'{os.path.basename(_basename)}_{vocal_name}_No_Reduction',) + + + if os.path.isfile(non_reduced_vocal_path): + file_exists_n = 'there' + else: + file_exists_n = 'not_there' + + if os.path.isfile(vocal_path): + file_exists_v = 'there' + else: + file_exists_v = 'not_there' + + if os.path.isfile(Instrumental_path): + file_exists_i = 'there' + else: + file_exists_i = 'not_there' + + print('Is there already a voc file there? ', file_exists_v) + + if not data['noisereduc_s'] == 'None': + c += 1 + + if not data['demucsmodel']: + + if data['inst_only']: + widget_text.write(base_text + 'Preparing to save Instrumental...') + else: + widget_text.write(base_text + 'Saving vocals... ') + + sf.write(non_reduced_vocal_path, sources[c].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + widget_text.write(base_text + 'Performing Noise Reduction... ') + reduction_sen = float(int(data['noisereduc_s'])/10) + subprocess.call("lib_v5\\sox\\sox.exe" + ' "' + + f"{str(non_reduced_vocal_path)}" + '" "' + f"{str(vocal_path)}" + '" ' + + "noisered lib_v5\\sox\\mdxnetnoisereduc.prof " + f"{reduction_sen}", + shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, stderr=subprocess.PIPE) + widget_text.write('Done!\n') + update_progress(**progress_kwargs, + step=(0.95)) + else: + if data['inst_only']: + widget_text.write(base_text + 'Preparing Instrumental...') + else: + widget_text.write(base_text + 'Saving Vocals... ') + + sf.write(non_reduced_vocal_path, sources[3].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + widget_text.write(base_text + 'Performing Noise Reduction... ') + reduction_sen = float(int(data['noisereduc_s'])/10) + subprocess.call("lib_v5\\sox\\sox.exe" + ' "' + + f"{str(non_reduced_vocal_path)}" + '" "' + f"{str(vocal_path)}" + '" ' + + "noisered lib_v5\\sox\\mdxnetnoisereduc.prof " + f"{reduction_sen}", + shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, stderr=subprocess.PIPE) + update_progress(**progress_kwargs, + step=(0.95)) + widget_text.write('Done!\n') + else: + c += 1 + + if not data['demucsmodel']: + if data['inst_only']: + widget_text.write(base_text + 'Preparing Instrumental...') + else: + widget_text.write(base_text + 'Saving Vocals... ') + sf.write(vocal_path, sources[c].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + else: + if data['inst_only']: + widget_text.write(base_text + 'Preparing Instrumental...') + else: + widget_text.write(base_text + 'Saving Vocals... ') + sf.write(vocal_path, sources[3].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + + if data['voc_only'] and not data['inst_only']: + pass + + else: + finalfiles = [ + { + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'files':[str(music_file), vocal_path], + } + ] + widget_text.write(base_text + 'Saving Instrumental... ') + for i, e in tqdm(enumerate(finalfiles)): + + wave, specs = {}, {} + + mp = ModelParameters(e['model_params']) + + for i in range(len(e['files'])): + spec = {} + + for d in range(len(mp.param['band']), 0, -1): + bp = mp.param['band'][d] + + if d == len(mp.param['band']): # high-end band + wave[d], _ = librosa.load( + e['files'][i], bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if len(wave[d].shape) == 1: # mono to stereo + wave[d] = np.array([wave[d], wave[d]]) + else: # lower bands + wave[d] = librosa.resample(wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + spec[d] = spec_utils.wave_to_spectrogram(wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], mp.param['mid_side_b2'], mp.param['reverse']) + + specs[i] = spec_utils.combine_spectrograms(spec, mp) + + del wave + + ln = min([specs[0].shape[2], specs[1].shape[2]]) + specs[0] = specs[0][:,:,:ln] + specs[1] = specs[1][:,:,:ln] + X_mag = np.abs(specs[0]) + y_mag = np.abs(specs[1]) + max_mag = np.where(X_mag >= y_mag, X_mag, y_mag) + v_spec = specs[1] - max_mag * np.exp(1.j * np.angle(specs[0])) + update_progress(**progress_kwargs, + step=(1)) + sf.write(Instrumental_path, spec_utils.cmb_spectrogram_to_wave(-v_spec, mp), mp.param['sr']) + + + if data['inst_only']: + if file_exists_v == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + + widget_text.write('Done!\n') + + + if data['saveFormat'] == 'Mp3': + try: + if data['inst_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(vocal_path) + musfile.export(vocal_path_mp3, format="mp3", bitrate="320k") + if file_exists_v == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + if data['voc_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(Instrumental_path) + musfile.export(Instrumental_path_mp3, format="mp3", bitrate="320k") + if file_exists_i == 'there': + pass + else: + try: + os.remove(Instrumental_path) + except: + pass + if data['non_red'] == True: + musfile = pydub.AudioSegment.from_wav(non_reduced_vocal_path) + musfile.export(non_reduced_vocal_path_mp3, format="mp3", bitrate="320k") + if file_exists_n == 'there': + pass + else: + try: + os.remove(non_reduced_vocal_path) + except: + pass + + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + widget_text.write(base_text + 'Failed to save output(s) as Mp3(s).\n') + widget_text.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + widget_text.write(base_text + 'Moving on...\n') + else: + widget_text.write(base_text + 'Failed to save output(s) as Mp3(s).\n') + widget_text.write(base_text + 'Please check error log.\n') + widget_text.write(base_text + 'Moving on...\n') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to save file as mp3 "{os.path.basename(music_file)}":\n\n' + + f'Process Method: MDX-Net\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + if data['saveFormat'] == 'Flac': + try: + if data['inst_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(vocal_path) + musfile.export(vocal_path_flac, format="flac") + if file_exists_v == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + if data['voc_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(Instrumental_path) + musfile.export(Instrumental_path_flac, format="flac") + if file_exists_i == 'there': + pass + else: + try: + os.remove(Instrumental_path) + except: + pass + if data['non_red'] == True: + musfile = pydub.AudioSegment.from_wav(non_reduced_vocal_path) + musfile.export(non_reduced_vocal_path_flac, format="flac") + if file_exists_n == 'there': + pass + else: + try: + os.remove(non_reduced_vocal_path) + except: + pass + + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + widget_text.write(base_text + 'Failed to save output(s) as Flac(s).\n') + widget_text.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + widget_text.write(base_text + 'Moving on...\n') + else: + widget_text.write(base_text + 'Failed to save output(s) as Flac(s).\n') + widget_text.write(base_text + 'Please check error log.\n') + widget_text.write(base_text + 'Moving on...\n') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to save file as flac "{os.path.basename(music_file)}":\n\n' + + f'Process Method: MDX-Net\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + + try: + print('Is there already a voc file there? ', file_exists_v) + print('Is there already a non_voc file there? ', file_exists_n) + except: + pass + + + + if data['noisereduc_s'] == 'None': + pass + elif data['non_red'] == True: + pass + elif data['inst_only']: + if file_exists_n == 'there': + pass + else: + try: + os.remove(non_reduced_vocal_path) + except: + pass + else: + try: + os.remove(non_reduced_vocal_path) + except: + pass + + widget_text.write(base_text + 'Completed Seperation!\n') + + def demix(self, mix): + # 1 = demucs only + # 0 = onnx only + if data['chunks'] == 'Full': + chunk_set = 0 + else: + chunk_set = data['chunks'] + + if data['chunks'] == 'Auto': + if data['gpu'] == 0: + try: + gpu_mem = round(torch.cuda.get_device_properties(0).total_memory/1.074e+9) + except: + widget_text.write(base_text + 'NVIDIA GPU Required for conversion!\n') + if int(gpu_mem) <= int(5): + chunk_set = int(5) + widget_text.write(base_text + 'Chunk size auto-set to 5... \n') + if gpu_mem in [6, 7]: + chunk_set = int(30) + widget_text.write(base_text + 'Chunk size auto-set to 30... \n') + if gpu_mem in [8, 9, 10, 11, 12, 13, 14, 15]: + chunk_set = int(40) + widget_text.write(base_text + 'Chunk size auto-set to 40... \n') + if int(gpu_mem) >= int(16): + chunk_set = int(60) + widget_text.write(base_text + 'Chunk size auto-set to 60... \n') + if data['gpu'] == -1: + sys_mem = psutil.virtual_memory().total >> 30 + if int(sys_mem) <= int(4): + chunk_set = int(1) + widget_text.write(base_text + 'Chunk size auto-set to 1... \n') + if sys_mem in [5, 6, 7, 8]: + chunk_set = int(10) + widget_text.write(base_text + 'Chunk size auto-set to 10... \n') + if sys_mem in [9, 10, 11, 12, 13, 14, 15, 16]: + chunk_set = int(25) + widget_text.write(base_text + 'Chunk size auto-set to 25... \n') + if int(sys_mem) >= int(17): + chunk_set = int(60) + widget_text.write(base_text + 'Chunk size auto-set to 60... \n') + elif data['chunks'] == 'Full': + chunk_set = 0 + widget_text.write(base_text + "Chunk size set to full... \n") + else: + chunk_set = int(data['chunks']) + widget_text.write(base_text + "Chunk size user-set to "f"{chunk_set}... \n") + + samples = mix.shape[-1] + margin = margin_set + chunk_size = chunk_set*44100 + assert not margin == 0, 'margin cannot be zero!' + if margin > chunk_size: + margin = chunk_size + + b = np.array([[[0.5]], [[0.5]], [[0.7]], [[0.9]]]) + segmented_mix = {} + + if chunk_set == 0 or samples < chunk_size: + chunk_size = samples + + counter = -1 + for skip in range(0, samples, chunk_size): + counter+=1 + + s_margin = 0 if counter == 0 else margin + end = min(skip+chunk_size+margin, samples) + + start = skip-s_margin + + segmented_mix[skip] = mix[:,start:end].copy() + if end == samples: + break + + if not data['demucsmodel']: + sources = self.demix_base(segmented_mix, margin_size=margin) + + else: # both, apply spec effects + base_out = self.demix_base(segmented_mix, margin_size=margin) + demucs_out = self.demix_demucs(segmented_mix, margin_size=margin) + nan_count = np.count_nonzero(np.isnan(demucs_out)) + np.count_nonzero(np.isnan(base_out)) + if nan_count > 0: + print('Warning: there are {} nan values in the array(s).'.format(nan_count)) + demucs_out, base_out = np.nan_to_num(demucs_out), np.nan_to_num(base_out) + sources = {} + + sources[3] = (spec_effects(wave=[demucs_out[3],base_out[0]], + algorithm='default', + value=b[3])*1.03597672895) # compensation + return sources + + def demix_base(self, mixes, margin_size): + chunked_sources = [] + onnxitera = len(mixes) + onnxitera_calc = onnxitera * 2 + gui_progress_bar_onnx = 0 + widget_text.write(base_text + "Running ONNX Inference...\n") + widget_text.write(base_text + "Processing "f"{onnxitera} slices... ") + print(' Running ONNX Inference...') + for mix in mixes: + gui_progress_bar_onnx += 1 + if data['demucsmodel']: + update_progress(**progress_kwargs, + step=(0.1 + (0.5/onnxitera_calc * gui_progress_bar_onnx))) + else: + update_progress(**progress_kwargs, + step=(0.1 + (0.9/onnxitera * gui_progress_bar_onnx))) + cmix = mixes[mix] + sources = [] + n_sample = cmix.shape[1] + + mod = 0 + for model in self.models: + mod += 1 + trim = model.n_fft//2 + gen_size = model.chunk_size-2*trim + pad = gen_size - n_sample%gen_size + mix_p = np.concatenate((np.zeros((2,trim)), cmix, np.zeros((2,pad)), np.zeros((2,trim))), 1) + mix_waves = [] + i = 0 + while i < n_sample + pad: + waves = np.array(mix_p[:, i:i+model.chunk_size]) + mix_waves.append(waves) + i += gen_size + mix_waves = torch.tensor(mix_waves, dtype=torch.float32).to(cpu) + with torch.no_grad(): + _ort = self.onnx_models[mod] + spek = model.stft(mix_waves) + + tar_waves = model.istft(torch.tensor(_ort.run(None, {'input': spek.cpu().numpy()})[0]))#.cpu() + + tar_signal = tar_waves[:,:,trim:-trim].transpose(0,1).reshape(2, -1).numpy()[:, :-pad] + + start = 0 if mix == 0 else margin_size + end = None if mix == list(mixes.keys())[::-1][0] else -margin_size + if margin_size == 0: + end = None + sources.append(tar_signal[:,start:end]) + + + chunked_sources.append(sources) + _sources = np.concatenate(chunked_sources, axis=-1) + del self.onnx_models + widget_text.write('Done!\n') + return _sources + + def demix_demucs(self, mix, margin_size): + processed = {} + demucsitera = len(mix) + demucsitera_calc = demucsitera * 2 + gui_progress_bar_demucs = 0 + widget_text.write(base_text + "Running Demucs Inference...\n") + widget_text.write(base_text + "Processing "f"{len(mix)} slices... ") + print(' Running Demucs Inference...') + for nmix in mix: + gui_progress_bar_demucs += 1 + update_progress(**progress_kwargs, + step=(0.35 + (1.05/demucsitera_calc * gui_progress_bar_demucs))) + cmix = mix[nmix] + cmix = torch.tensor(cmix, dtype=torch.float32) + ref = cmix.mean(0) + cmix = (cmix - ref.mean()) / ref.std() + shift_set = 0 + with torch.no_grad(): + sources = apply_model(self.demucs, cmix.to(device), split=True, overlap=overlap_set, shifts=shift_set) + sources = (sources * ref.std() + ref.mean()).cpu().numpy() + sources[[0,1]] = sources[[1,0]] + + start = 0 if nmix == 0 else margin_size + end = None if nmix == list(mix.keys())[::-1][0] else -margin_size + if margin_size == 0: + end = None + processed[nmix] = sources[:,:,start:end].copy() + + sources = list(processed.values()) + sources = np.concatenate(sources, axis=-1) + widget_text.write('Done!\n') + return sources + +data = { + # Paths + 'input_paths': None, + 'export_path': None, + 'saveFormat': 'Wav', + # Processing Options + 'demucsmodel': True, + 'gpu': -1, + 'chunks': 10, + 'non_red': False, + 'noisereduc_s': 3, + 'mixing': 'default', + 'modelFolder': False, + 'voc_only': False, + 'inst_only': False, + 'break': False, + # Choose Model + 'mdxnetModel': 'UVR-MDX-NET 1', + 'high_end_process': 'mirroring', +} +default_chunks = data['chunks'] +default_noisereduc_s = data['noisereduc_s'] + +def update_progress(progress_var, total_files, file_num, step: float = 1): + """Calculate the progress for the progress widget in the GUI""" + base = (100 / total_files) + progress = base * (file_num - 1) + progress += base * step + + progress_var.set(progress) + +def get_baseText(total_files, file_num): + """Create the base text for the command widget""" + text = 'File {file_num}/{total_files} '.format(file_num=file_num, + total_files=total_files) + return text + +warnings.filterwarnings("ignore") +cpu = torch.device('cpu') +device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') + +def hide_opt(): + with open(os.devnull, "w") as devnull: + old_stdout = sys.stdout + sys.stdout = devnull + try: + yield + finally: + sys.stdout = old_stdout + +def main(window: tk.Wm, text_widget: tk.Text, button_widget: tk.Button, progress_var: tk.Variable, + **kwargs: dict): + + global widget_text + global gui_progress_bar + global music_file + global channel_set + global margin_set + global overlap_set + global default_chunks + global default_noisereduc_s + global _basename + global _mixture + global progress_kwargs + global base_text + global model_set + global model_set_name + + # Update default settings + default_chunks = data['chunks'] + default_noisereduc_s = data['noisereduc_s'] + + channel_set = int(64) + margin_set = int(44100) + overlap_set = float(0.5) + + widget_text = text_widget + gui_progress_bar = progress_var + + #Error Handling + + onnxmissing = "[ONNXRuntimeError] : 3 : NO_SUCHFILE" + runtimeerr = "CUDNN error executing cudnnSetTensorNdDescriptor" + cuda_err = "CUDA out of memory" + mod_err = "ModuleNotFoundError" + file_err = "FileNotFoundError" + ffmp_err = """audioread\__init__.py", line 116, in audio_open""" + sf_write_err = "sf.write" + + try: + with open('errorlog.txt', 'w') as f: + f.write(f'No errors to report at this time.' + f'\n\nLast Process Method Used: MDX-Net' + + f'\nLast Conversion Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + data.update(kwargs) + + if data['mdxnetModel'] == 'UVR-MDX-NET 1': + model_set = 'UVR_MDXNET_9703.onnx' + model_set_name = 'UVR_MDXNET_9703' + if data['mdxnetModel'] == 'UVR-MDX-NET 2': + model_set = 'UVR_MDXNET_9682.onnx' + model_set_name = 'UVR_MDXNET_9682' + if data['mdxnetModel'] == 'UVR-MDX-NET 3': + model_set = 'UVR_MDXNET_9662.onnx' + model_set_name = 'UVR_MDXNET_9662' + if data['mdxnetModel'] == 'UVR-MDX-NET Karaoke': + model_set = 'UVR_MDXNET_KARA.onnx' + model_set_name = 'UVR_MDXNET_Karaoke' + + stime = time.perf_counter() + progress_var.set(0) + text_widget.clear() + button_widget.configure(state=tk.DISABLED) # Disable Button + + try: #Load File(s) + for file_num, music_file in tqdm(enumerate(data['input_paths'], start=1)): + + _mixture = f'{data["input_paths"]}' + _basename = f'{data["export_path"]}/{file_num}_{os.path.splitext(os.path.basename(music_file))[0]}' + + # -Get text and update progress- + base_text = get_baseText(total_files=len(data['input_paths']), + file_num=file_num) + progress_kwargs = {'progress_var': progress_var, + 'total_files': len(data['input_paths']), + 'file_num': file_num} + + try: + total, used, free = shutil.disk_usage("/") + + total_space = int(total/1.074e+9) + used_space = int(used/1.074e+9) + free_space = int(free/1.074e+9) + + if int(free/1.074e+9) <= int(2): + text_widget.write('Error: Not enough storage on main drive to continue. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly. \n\nPlease ensure your main drive has at least 3 GB\'s of storage and try again.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n') + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if int(free/1.074e+9) in [3, 4, 5, 6, 7, 8]: + text_widget.write('Warning: Your main drive is running low on storage. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n\n') + except: + pass + + if data['noisereduc_s'] == 'None': + pass + else: + if not os.path.isfile("lib_v5\sox\sox.exe"): + data['noisereduc_s'] = 'None' + data['non_red'] = False + widget_text.write(base_text + 'SoX is missing and required for noise reduction.\n') + widget_text.write(base_text + 'See the \"More Info\" tab in the Help Guide.\n') + widget_text.write(base_text + 'Noise Reduction will be disabled until SoX is available.\n\n') + + update_progress(**progress_kwargs, + step=0) + + e = os.path.join(data["export_path"]) + + demucsmodel = 'models/Demucs_Model/demucs_extra-3646af93_org.th' + + pred = Predictor() + pred.prediction_setup(demucs_name=demucsmodel, + channels=channel_set) + + # split + pred.prediction( + m=music_file, + ) + + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + message = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if runtimeerr in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Your PC cannot process this audio file with the chunk size selected.\nPlease lower the chunk size and try again.\n\n') + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'Your PC cannot process this audio file with the chunk size selected.\nPlease lower the chunk size and try again.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + message + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if cuda_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'The application was unable to allocate enough GPU memory to use this model.\n') + text_widget.write(f'Please close any GPU intensive applications and try again.\n') + text_widget.write(f'If the error persists, your GPU might not be supported.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'The application was unable to allocate enough GPU memory to use this model.\n' + + f'Please close any GPU intensive applications and try again.\n' + + f'If the error persists, your GPU might not be supported.\n\n' + + f'Raw error details:\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if mod_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Application files(s) are missing.\n') + text_widget.write("\n" + f'{type(e).__name__} - "{e}"' + "\n\n") + text_widget.write(f'Please check for missing files/scripts in the app directory and try again.\n') + text_widget.write(f'If the error persists, please reinstall application or contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'Application files(s) are missing.\n' + + f'Please check for missing files/scripts in the app directory and try again.\n' + + f'If the error persists, please reinstall application or contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if file_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Missing file error raised.\n') + text_widget.write("\n" + f'{type(e).__name__} - "{e}"' + "\n\n") + text_widget.write("\n" + f'Please address the error and try again.' + "\n") + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'Missing file error raised.\n' + + "\n" + f'Please address the error and try again.' + "\n" + + f'If this error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if ffmp_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'FFmpeg is missing or corrupt.\n') + text_widget.write(f'You will only be able to process .wav files until FFmpeg is available on this system.\n') + text_widget.write(f'See the \"More Info\" tab in the Help Guide.\n\n') + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'FFmpeg is missing or corrupt.\n' + + f'You will only be able to process .wav files until FFmpeg is available on this system.\n' + + f'See the \"More Info\" tab in the Help Guide.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if onnxmissing in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'The application could not detect this MDX-Net model on your system.\n') + text_widget.write(f'Please make sure all the models are present in the correct directory.\n') + text_widget.write(f'If the error persists, please reinstall application or contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'The application could not detect this MDX-Net model on your system.\n' + + f'Please make sure all the models are present in the correct directory.\n' + + f'If the error persists, please reinstall application or contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if sf_write_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Could not write audio file.\n') + text_widget.write(f'This could be due to low storage on target device or a system permissions issue.\n') + text_widget.write(f"\nFor raw error details, go to the Error Log tab in the Help Guide.\n") + text_widget.write(f'\nIf the error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'Could not write audio file.\n' + + f'This could be due to low storage on target device or a system permissions issue.\n' + + f'If the error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + print(traceback_text) + print(type(e).__name__, e) + print(message) + + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: MDX-Net\n\n' + + f'If this error persists, please contact the developers with the error details.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + tk.messagebox.showerror(master=window, + title='Error Details', + message=message) + progress_var.set(0) + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n') + text_widget.write("\nFor raw error details, go to the Error Log tab in the Help Guide.\n") + text_widget.write("\n" + f'Please address the error and try again.' + "\n") + text_widget.write(f'If this error persists, please contact the developers with the error details.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + progress_var.set(0) + + text_widget.write(f'\nConversion(s) Completed!\n') + + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') # nopep8 + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) # Enable Button + +if __name__ == '__main__': + start_time = time.time() + main() + print("Successfully completed music demixing.");print('Total time: {0:.{1}f}s'.format(time.time() - start_time, 1)) + diff --git a/inference_v5.py b/inference_v5.py new file mode 100644 index 0000000..4c1b1ae --- /dev/null +++ b/inference_v5.py @@ -0,0 +1,1022 @@ +from functools import total_ordering +import os +import importlib +from statistics import mode +import pydub +import shutil +import hashlib + +import cv2 +import librosa +import math +import numpy as np +import soundfile as sf +from tqdm import tqdm + +from lib_v5 import dataset +from lib_v5 import spec_utils +from lib_v5.model_param_init import ModelParameters +import torch +from datetime import datetime + +# Command line text parsing and widget manipulation +from collections import defaultdict +import tkinter as tk +import traceback # Error Message Recent Calls +import time # Timer + +class VocalRemover(object): + + def __init__(self, data, text_widget: tk.Text): + self.data = data + self.text_widget = text_widget + self.models = defaultdict(lambda: None) + self.devices = defaultdict(lambda: None) + # self.offset = model.offset + +data = { + # Paths + 'input_paths': None, + 'export_path': None, + 'saveFormat': 'wav', + # Processing Options + 'gpu': -1, + 'postprocess': True, + 'tta': True, + 'output_image': True, + 'voc_only': False, + 'inst_only': False, + # Models + 'instrumentalModel': None, + 'useModel': None, + # Constants + 'window_size': 512, + 'agg': 10, + 'high_end_process': 'mirroring' +} + +default_window_size = data['window_size'] +default_agg = data['agg'] + +def update_progress(progress_var, total_files, file_num, step: float = 1): + """Calculate the progress for the progress widget in the GUI""" + base = (100 / total_files) + progress = base * (file_num - 1) + progress += base * step + + progress_var.set(progress) + +def get_baseText(total_files, file_num): + """Create the base text for the command widget""" + text = 'File {file_num}/{total_files} '.format(file_num=file_num, + total_files=total_files) + return text + +def determineModelFolderName(): + """ + Determine the name that is used for the folder and appended + to the back of the music files + """ + modelFolderName = '' + if not data['modelFolder']: + # Model Test Mode not selected + return modelFolderName + + # -Instrumental- + if os.path.isfile(data['instrumentalModel']): + modelFolderName += os.path.splitext(os.path.basename(data['instrumentalModel']))[0] + + if modelFolderName: + modelFolderName = '/' + modelFolderName + + return modelFolderName + +def main(window: tk.Wm, text_widget: tk.Text, button_widget: tk.Button, progress_var: tk.Variable, + **kwargs: dict): + + global model_params_d + global nn_arch_sizes + global nn_architecture + + runtimeerr = "CUDNN error executing cudnnSetTensorNdDescriptor" + cuda_err = "CUDA out of memory" + mod_err = "ModuleNotFoundError" + file_err = "FileNotFoundError" + ffmp_err = """audioread\__init__.py", line 116, in audio_open""" + sf_write_err = "sf.write" + + try: + with open('errorlog.txt', 'w') as f: + f.write(f'No errors to report at this time.' + f'\n\nLast Process Method Used: VR Architecture' + + f'\nLast Conversion Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + nn_arch_sizes = [ + 31191, # default + 33966, 123821, 123812, 537238 # custom + ] + + nn_architecture = list('{}KB'.format(s) for s in nn_arch_sizes) + + + def save_files(wav_instrument, wav_vocals): + """Save output music files""" + vocal_name = '(Vocals)' + instrumental_name = '(Instrumental)' + save_path = os.path.dirname(base_name) + + # Swap names if vocal model + + VModel="Vocal" + + if VModel in model_name: + # Reverse names + vocal_name, instrumental_name = instrumental_name, vocal_name + + # Save Temp File + # For instrumental the instrumental is the temp file + # and for vocal the instrumental is the temp file due + # to reversement + sf.write(f'temp.wav', + wav_instrument, mp.param['sr']) + + appendModelFolderName = modelFolderName.replace('/', '_') + + # -Save files- + # Instrumental + if instrumental_name is not None: + if data['modelFolder']: + instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}{appendModelFolderName}_{instrumental_name}',) + instrumental_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}{appendModelFolderName}_{instrumental_name}',) + instrumental_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}{appendModelFolderName}_{instrumental_name}',) + else: + instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{instrumental_name}',) + instrumental_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{instrumental_name}',) + instrumental_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{instrumental_name}',) + + if os.path.isfile(instrumental_path): + file_exists_i = 'there' + else: + file_exists_i = 'not_there' + + if VModel in model_name and data['voc_only']: + sf.write(instrumental_path, + wav_instrument, mp.param['sr']) + elif VModel in model_name and data['inst_only']: + pass + elif data['voc_only']: + pass + else: + sf.write(instrumental_path, + wav_instrument, mp.param['sr']) + + # Vocal + if vocal_name is not None: + if data['modelFolder']: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}{appendModelFolderName}_{vocal_name}',) + vocal_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}{appendModelFolderName}_{vocal_name}',) + vocal_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}{appendModelFolderName}_{vocal_name}',) + else: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{vocal_name}',) + vocal_path_mp3 = '{save_path}/{file_name}.mp3'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{vocal_name}',) + vocal_path_flac = '{save_path}/{file_name}.flac'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{vocal_name}',) + + if os.path.isfile(vocal_path): + file_exists_v = 'there' + else: + file_exists_v = 'not_there' + + if VModel in model_name and data['inst_only']: + sf.write(vocal_path, + wav_vocals, mp.param['sr']) + elif VModel in model_name and data['voc_only']: + pass + elif data['inst_only']: + pass + else: + sf.write(vocal_path, + wav_vocals, mp.param['sr']) + + if data['saveFormat'] == 'Mp3': + try: + if data['inst_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(vocal_path) + musfile.export(vocal_path_mp3, format="mp3", bitrate="320k") + if file_exists_v == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + if data['voc_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(instrumental_path) + musfile.export(instrumental_path_mp3, format="mp3", bitrate="320k") + if file_exists_i == 'there': + pass + else: + try: + os.remove(instrumental_path) + except: + pass + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + text_widget.write(base_text + 'Failed to save output(s) as Mp3(s).\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + else: + text_widget.write(base_text + 'Failed to save output(s) as Mp3(s).\n') + text_widget.write(base_text + 'Please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to save file as mp3 "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + if data['saveFormat'] == 'Flac': + try: + if VModel in model_name: + if data['inst_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(instrumental_path) + musfile.export(instrumental_path_flac, format="flac") + if file_exists_v == 'there': + pass + else: + try: + os.remove(instrumental_path) + except: + pass + if data['voc_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(vocal_path) + musfile.export(vocal_path_flac, format="flac") + if file_exists_i == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + else: + if data['inst_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(vocal_path) + musfile.export(vocal_path_flac, format="flac") + if file_exists_v == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + if data['voc_only'] == True: + pass + else: + musfile = pydub.AudioSegment.from_wav(instrumental_path) + musfile.export(instrumental_path_flac, format="flac") + if file_exists_i == 'there': + pass + else: + try: + os.remove(instrumental_path) + except: + pass + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + text_widget.write(base_text + 'Failed to save output(s) as Flac(s).\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + else: + text_widget.write(base_text + 'Failed to save output(s) as Flac(s).\n') + text_widget.write(base_text + 'Please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to save file as flac "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + + data.update(kwargs) + + # Update default settings + global default_window_size + global default_agg + default_window_size = data['window_size'] + default_agg = data['agg'] + + stime = time.perf_counter() + progress_var.set(0) + text_widget.clear() + button_widget.configure(state=tk.DISABLED) # Disable Button + + vocal_remover = VocalRemover(data, text_widget) + modelFolderName = determineModelFolderName() + + # Separation Preperation + try: #Load File(s) + for file_num, music_file in enumerate(data['input_paths'], start=1): + # Determine File Name + base_name = f'{data["export_path"]}/{file_num}_{os.path.splitext(os.path.basename(music_file))[0]}' + + model_name = os.path.basename(data[f'{data["useModel"]}Model']) + model = vocal_remover.models[data['useModel']] + device = vocal_remover.devices[data['useModel']] + # -Get text and update progress- + base_text = get_baseText(total_files=len(data['input_paths']), + file_num=file_num) + progress_kwargs = {'progress_var': progress_var, + 'total_files': len(data['input_paths']), + 'file_num': file_num} + update_progress(**progress_kwargs, + step=0) + + try: + total, used, free = shutil.disk_usage("/") + + total_space = int(total/1.074e+9) + used_space = int(used/1.074e+9) + free_space = int(free/1.074e+9) + + if int(free/1.074e+9) <= int(2): + text_widget.write('Error: Not enough storage on main drive to continue. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly. \n\nPlease ensure your main drive has at least 3 GB\'s of storage and try again.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n') + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if int(free/1.074e+9) in [3, 4, 5, 6, 7, 8]: + text_widget.write('Warning: Your main drive is running low on storage. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n\n') + except: + pass + + #Load Model + text_widget.write(base_text + 'Loading models...') + + model_size = math.ceil(os.stat(data['instrumentalModel']).st_size / 1024) + nn_architecture = '{}KB'.format(min(nn_arch_sizes, key=lambda x:abs(x-model_size))) + + nets = importlib.import_module('lib_v5.nets' + f'_{nn_architecture}'.replace('_{}KB'.format(nn_arch_sizes[0]), ''), package=None) + + aggresive_set = float(data['agg']/100) + + ModelName=(data['instrumentalModel']) + + #Package Models + + model_hash = hashlib.md5(open(ModelName,'rb').read()).hexdigest() + print(model_hash) + + #v5 Models + + if model_hash == '47939caf0cfe52a0e81442b85b971dfd': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == '4e4ecb9764c50a8c414fee6e10395bbe': + model_params_d=str('lib_v5/modelparams/4band_v2.json') + param_name=str('4band_v2') + if model_hash == 'e60a1e84803ce4efc0a6551206cc4b71': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == 'a82f14e75892e55e994376edbf0c8435': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == '6dd9eaa6f0420af9f1d403aaafa4cc06': + model_params_d=str('lib_v5/modelparams/4band_v2_sn.json') + param_name=str('4band_v2_sn') + if model_hash == '5c7bbca45a187e81abbbd351606164e5': + model_params_d=str('lib_v5/modelparams/3band_44100_msb2.json') + param_name=str('3band_44100_msb2') + if model_hash == 'd6b2cb685a058a091e5e7098192d3233': + model_params_d=str('lib_v5/modelparams/3band_44100_msb2.json') + param_name=str('3band_44100_msb2') + if model_hash == 'c1b9f38170a7c90e96f027992eb7c62b': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == 'c3448ec923fa0edf3d03a19e633faa53': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + + #v4 Models + + if model_hash == '6a00461c51c2920fd68937d4609ed6c8': + model_params_d=str('lib_v5/modelparams/1band_sr16000_hl512.json') + param_name=str('1band_sr16000_hl512') + if model_hash == '0ab504864d20f1bd378fe9c81ef37140': + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if model_hash == '7dd21065bf91c10f7fccb57d7d83b07f': + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if model_hash == '80ab74d65e515caa3622728d2de07d23': + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if model_hash == 'edc115e7fc523245062200c00caa847f': + model_params_d=str('lib_v5/modelparams/1band_sr33075_hl384.json') + param_name=str('1band_sr33075_hl384') + if model_hash == '28063e9f6ab5b341c5f6d3c67f2045b7': + model_params_d=str('lib_v5/modelparams/1band_sr33075_hl384.json') + param_name=str('1band_sr33075_hl384') + if model_hash == 'b58090534c52cbc3e9b5104bad666ef2': + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl512.json') + param_name=str('1band_sr44100_hl512') + if model_hash == '0cdab9947f1b0928705f518f3c78ea8f': + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl512.json') + param_name=str('1band_sr44100_hl512') + if model_hash == 'ae702fed0238afb5346db8356fe25f13': + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl1024.json') + param_name=str('1band_sr44100_hl1024') + + #User Models + + #1 Band + if '1band_sr16000_hl512' in ModelName: + model_params_d=str('lib_v5/modelparams/1band_sr16000_hl512.json') + param_name=str('1band_sr16000_hl512') + if '1band_sr32000_hl512' in ModelName: + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if '1band_sr33075_hl384' in ModelName: + model_params_d=str('lib_v5/modelparams/1band_sr33075_hl384.json') + param_name=str('1band_sr33075_hl384') + if '1band_sr44100_hl256' in ModelName: + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl256.json') + param_name=str('1band_sr44100_hl256') + if '1band_sr44100_hl512' in ModelName: + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl512.json') + param_name=str('1band_sr44100_hl512') + if '1band_sr44100_hl1024' in ModelName: + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl1024.json') + param_name=str('1band_sr44100_hl1024') + + #2 Band + if '2band_44100_lofi' in ModelName: + model_params_d=str('lib_v5/modelparams/2band_44100_lofi.json') + param_name=str('2band_44100_lofi') + if '2band_32000' in ModelName: + model_params_d=str('lib_v5/modelparams/2band_32000.json') + param_name=str('2band_32000') + if '2band_48000' in ModelName: + model_params_d=str('lib_v5/modelparams/2band_48000.json') + param_name=str('2band_48000') + + #3 Band + if '3band_44100' in ModelName: + model_params_d=str('lib_v5/modelparams/3band_44100.json') + param_name=str('3band_44100') + if '3band_44100_mid' in ModelName: + model_params_d=str('lib_v5/modelparams/3band_44100_mid.json') + param_name=str('3band_44100_mid') + if '3band_44100_msb2' in ModelName: + model_params_d=str('lib_v5/modelparams/3band_44100_msb2.json') + param_name=str('3band_44100_msb2') + + #4 Band + if '4band_44100' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if '4band_44100_mid' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_44100_mid.json') + param_name=str('4band_44100_mid') + if '4band_44100_msb' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_44100_msb.json') + param_name=str('4band_44100_msb') + if '4band_44100_msb2' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_44100_msb2.json') + param_name=str('4band_44100_msb2') + if '4band_44100_reverse' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_44100_reverse.json') + param_name=str('4band_44100_reverse') + if '4band_44100_sw' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_44100_sw.json') + param_name=str('4band_44100_sw') + if '4band_v2' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_v2.json') + param_name=str('4band_v2') + if '4band_v2_sn' in ModelName: + model_params_d=str('lib_v5/modelparams/4band_v2_sn.json') + param_name=str('4band_v2_sn') + if 'tmodelparam' in ModelName: + model_params_d=str('lib_v5/modelparams/tmodelparam.json') + param_name=str('User Model Param Set') + + text_widget.write(' Done!\n') + + try: + print('Model Parameters:', model_params_d) + text_widget.write(base_text + 'Loading assigned model parameters ' + '\"' + param_name + '\"... ') + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Model parameters are missing.\n\n') + text_widget.write(f'Please check the following:\n') + text_widget.write(f'1. Make sure the model is still present.\n') + text_widget.write(f'2. If you are running a model that was not originally included in this package, \nplease append the modelparam name to the model name.\n') + text_widget.write(f' - Example if using \"4band_v2.json\" modelparam: \"model_4band_v2.pth\"\n\n') + text_widget.write(f'Please address this and try again.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'Model parameters are missing.\n\n' + + f'Please check the following:\n' + + f'1. Make sure the model is still present.\n' + + f'2. If you are running a model that was not originally included in this package, please append the modelparam name to the model name.\n' + + f' - Example if using \"4band_v2.json\" modelparam: \"model_4band_v2.pth\"\n\n' + + f'Please address this and try again.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + mp = ModelParameters(model_params_d) + text_widget.write('Done!\n') + # -Instrumental- + if os.path.isfile(data['instrumentalModel']): + device = torch.device('cpu') + model = nets.CascadedASPPNet(mp.param['bins'] * 2) + model.load_state_dict(torch.load(data['instrumentalModel'], + map_location=device)) + if torch.cuda.is_available() and data['gpu'] >= 0: + device = torch.device('cuda:{}'.format(data['gpu'])) + model.to(device) + + vocal_remover.models['instrumental'] = model + vocal_remover.devices['instrumental'] = device + + + model_name = os.path.basename(data[f'{data["useModel"]}Model']) + + mp = ModelParameters(model_params_d) + + # -Go through the different steps of seperation- + # Wave source + text_widget.write(base_text + 'Loading audio source...') + + X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {} + + bands_n = len(mp.param['band']) + + for d in range(bands_n, 0, -1): + bp = mp.param['band'][d] + + if d == bands_n: # high-end band + X_wave[d], _ = librosa.load( + music_file, bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if X_wave[d].ndim == 1: + X_wave[d] = np.asarray([X_wave[d], X_wave[d]]) + else: # lower bands + X_wave[d] = librosa.resample(X_wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + # Stft of wave source + + X_spec_s[d] = spec_utils.wave_to_spectrogram_mt(X_wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], + mp.param['mid_side_b2'], mp.param['reverse']) + + if d == bands_n and data['high_end_process'] != 'none': + input_high_end_h = (bp['n_fft']//2 - bp['crop_stop']) + (mp.param['pre_filter_stop'] - mp.param['pre_filter_start']) + input_high_end = X_spec_s[d][:, bp['n_fft']//2-input_high_end_h:bp['n_fft']//2, :] + + text_widget.write('Done!\n') + + update_progress(**progress_kwargs, + step=0.1) + + text_widget.write(base_text + 'Loading the stft of audio source...') + + text_widget.write(' Done!\n') + + text_widget.write(base_text + "Please Wait...\n") + + X_spec_m = spec_utils.combine_spectrograms(X_spec_s, mp) + + del X_wave, X_spec_s + + def inference(X_spec, device, model, aggressiveness): + + def _execute(X_mag_pad, roi_size, n_window, device, model, aggressiveness): + model.eval() + + with torch.no_grad(): + preds = [] + + iterations = [n_window] + + total_iterations = sum(iterations) + + text_widget.write(base_text + "Processing "f"{total_iterations} Slices... ") + + for i in tqdm(range(n_window)): + update_progress(**progress_kwargs, + step=(0.1 + (0.8/n_window * i))) + start = i * roi_size + X_mag_window = X_mag_pad[None, :, :, start:start + data['window_size']] + X_mag_window = torch.from_numpy(X_mag_window).to(device) + + pred = model.predict(X_mag_window, aggressiveness) + + pred = pred.detach().cpu().numpy() + preds.append(pred[0]) + + pred = np.concatenate(preds, axis=2) + text_widget.write('Done!\n') + return pred + + def preprocess(X_spec): + X_mag = np.abs(X_spec) + X_phase = np.angle(X_spec) + + return X_mag, X_phase + + X_mag, X_phase = preprocess(X_spec) + + coef = X_mag.max() + X_mag_pre = X_mag / coef + + n_frame = X_mag_pre.shape[2] + pad_l, pad_r, roi_size = dataset.make_padding(n_frame, + data['window_size'], model.offset) + n_window = int(np.ceil(n_frame / roi_size)) + + X_mag_pad = np.pad( + X_mag_pre, ((0, 0), (0, 0), (pad_l, pad_r)), mode='constant') + + pred = _execute(X_mag_pad, roi_size, n_window, + device, model, aggressiveness) + pred = pred[:, :, :n_frame] + + if data['tta']: + pad_l += roi_size // 2 + pad_r += roi_size // 2 + n_window += 1 + + X_mag_pad = np.pad( + X_mag_pre, ((0, 0), (0, 0), (pad_l, pad_r)), mode='constant') + + pred_tta = _execute(X_mag_pad, roi_size, n_window, + device, model, aggressiveness) + pred_tta = pred_tta[:, :, roi_size // 2:] + pred_tta = pred_tta[:, :, :n_frame] + + return (pred + pred_tta) * 0.5 * coef, X_mag, np.exp(1.j * X_phase) + else: + return pred * coef, X_mag, np.exp(1.j * X_phase) + + aggressiveness = {'value': aggresive_set, 'split_bin': mp.param['band'][1]['crop_stop']} + + if data['tta']: + text_widget.write(base_text + "Running Inferences (TTA)...\n") + else: + text_widget.write(base_text + "Running Inference...\n") + + pred, X_mag, X_phase = inference(X_spec_m, + device, + model, aggressiveness) + + update_progress(**progress_kwargs, + step=0.9) + # Postprocess + if data['postprocess']: + try: + text_widget.write(base_text + 'Post processing...') + pred_inv = np.clip(X_mag - pred, 0, np.inf) + pred = spec_utils.mask_silence(pred, pred_inv) + text_widget.write(' Done!\n') + except Exception as e: + text_widget.write('\n' + base_text + 'Post process failed, check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to run Post Processing on "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + update_progress(**progress_kwargs, + step=0.95) + + # Inverse stft + y_spec_m = pred * X_phase + v_spec_m = X_spec_m - y_spec_m + + if data['voc_only'] and not data['inst_only']: + pass + else: + text_widget.write(base_text + 'Saving Instrumental... ') + + if data['high_end_process'].startswith('mirroring'): + input_high_end_ = spec_utils.mirroring(data['high_end_process'], y_spec_m, input_high_end, mp) + wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, mp, input_high_end_h, input_high_end_) + if data['voc_only'] and not data['inst_only']: + pass + else: + text_widget.write('Done!\n') + else: + wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, mp) + if data['voc_only'] and not data['inst_only']: + pass + else: + text_widget.write('Done!\n') + + if data['inst_only'] and not data['voc_only']: + pass + else: + text_widget.write(base_text + 'Saving Vocals... ') + + if data['high_end_process'].startswith('mirroring'): + input_high_end_ = spec_utils.mirroring(data['high_end_process'], v_spec_m, input_high_end, mp) + + wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, mp, input_high_end_h, input_high_end_) + if data['inst_only'] and not data['voc_only']: + pass + else: + text_widget.write('Done!\n') + else: + wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, mp) + if data['inst_only'] and not data['voc_only']: + pass + else: + text_widget.write('Done!\n') + + update_progress(**progress_kwargs, + step=1) + + # Save output music files + save_files(wav_instrument, wav_vocals) + + update_progress(**progress_kwargs, + step=1) + + # Save output image + if data['output_image']: + with open('{}_Instruments.jpg'.format(base_name), mode='wb') as f: + image = spec_utils.spectrogram_to_image(y_spec_m) + _, bin_image = cv2.imencode('.jpg', image) + bin_image.tofile(f) + with open('{}_Vocals.jpg'.format(base_name), mode='wb') as f: + image = spec_utils.spectrogram_to_image(v_spec_m) + _, bin_image = cv2.imencode('.jpg', image) + bin_image.tofile(f) + + + text_widget.write(base_text + 'Completed Seperation!\n\n') + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + message = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if runtimeerr in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Your PC cannot process this audio file with the chunk size selected.\nPlease lower the chunk size and try again.\n\n') + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'Your PC cannot process this audio file with the chunk size selected.\nPlease lower the chunk size and try again.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + message + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if cuda_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'The application was unable to allocate enough GPU memory to use this model.\n') + text_widget.write(f'Please close any GPU intensive applications and try again.\n') + text_widget.write(f'If the error persists, your GPU might not be supported.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'The application was unable to allocate enough GPU memory to use this model.\n' + + f'Please close any GPU intensive applications and try again.\n' + + f'If the error persists, your GPU might not be supported.\n\n' + + f'Raw error details:\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if mod_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Application files(s) are missing.\n') + text_widget.write("\n" + f'{type(e).__name__} - "{e}"' + "\n\n") + text_widget.write(f'Please check for missing files/scripts in the app directory and try again.\n') + text_widget.write(f'If the error persists, please reinstall application or contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'Application files(s) are missing.\n' + + f'Please check for missing files/scripts in the app directory and try again.\n' + + f'If the error persists, please reinstall application or contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if file_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Missing file error raised.\n') + text_widget.write("\n" + f'{type(e).__name__} - "{e}"' + "\n\n") + text_widget.write("\n" + f'Please address the error and try again.' + "\n") + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'Missing file error raised.\n' + + "\n" + f'Please address the error and try again.' + "\n" + + f'If this error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if ffmp_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'FFmpeg is missing or corrupt.\n') + text_widget.write(f'You will only be able to process .wav files until FFmpeg is available on this system.\n') + text_widget.write(f'See the \"More Info\" tab in the Help Guide.\n\n') + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'FFmpeg is missing or corrupt.\n' + + f'You will only be able to process .wav files until FFmpeg is available on this system.\n' + + f'See the \"More Info\" tab in the Help Guide.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if sf_write_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Could not write audio file.\n') + text_widget.write(f'This could be due to low storage on target device or a system permissions issue.\n') + text_widget.write(f"\nFor raw error details, go to the Error Log tab in the Help Guide.\n") + text_widget.write(f'\nIf the error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'Could not write audio file.\n' + + f'This could be due to low storage on target device or a system permissions issue.\n' + + f'If the error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + print(traceback_text) + print(type(e).__name__, e) + print(message) + + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: VR Architecture\n\n' + + f'If this error persists, please contact the developers with the error details.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + tk.messagebox.showerror(master=window, + title='Error Details', + message=message) + progress_var.set(0) + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n') + text_widget.write("\nFor raw error details, go to the Error Log tab in the Help Guide.\n") + text_widget.write("\n" + f'Please address the error and try again.' + "\n") + text_widget.write(f'If this error persists, please contact the developers with the error details.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + try: + os.remove('temp.wav') + except: + pass + + progress_var.set(0) + text_widget.write(f'Conversion(s) Completed!\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') # nopep8 + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) # Enable Button \ No newline at end of file diff --git a/inference_v5_ensemble.py b/inference_v5_ensemble.py new file mode 100644 index 0000000..97f26ec --- /dev/null +++ b/inference_v5_ensemble.py @@ -0,0 +1,2049 @@ +from functools import total_ordering +import importlib +import os +from statistics import mode +from pathlib import Path +import pydub +import hashlib + +import subprocess +import soundfile as sf +import torch +import numpy as np +from demucs.model import Demucs +from demucs.utils import apply_model +from models import get_models, spec_effects +import onnxruntime as ort +import time +import os +from tqdm import tqdm +import warnings +import sys +import librosa +import psutil + +import cv2 +import math +import librosa +import numpy as np +import soundfile as sf +import shutil +from tqdm import tqdm +from datetime import datetime + +from lib_v5 import dataset +from lib_v5 import spec_utils +from lib_v5.model_param_init import ModelParameters +import torch + +# Command line text parsing and widget manipulation +from collections import defaultdict +import tkinter as tk +import traceback # Error Message Recent Calls +import time # Timer + +class Predictor(): + def __init__(self): + pass + + def prediction_setup(self, demucs_name, + channels=64): + if data['demucsmodel']: + self.demucs = Demucs(sources=["drums", "bass", "other", "vocals"], channels=channels) + widget_text.write(base_text + 'Loading Demucs model... ') + update_progress(**progress_kwargs, + step=0.05) + self.demucs.to(device) + self.demucs.load_state_dict(torch.load(demucs_name)) + widget_text.write('Done!\n') + self.demucs.eval() + self.onnx_models = {} + c = 0 + + self.models = get_models('tdf_extra', load=False, device=cpu, stems='vocals') + widget_text.write(base_text + 'Loading ONNX model... ') + update_progress(**progress_kwargs, + step=0.1) + c+=1 + + if data['gpu'] >= 0: + if torch.cuda.is_available(): + run_type = ['CUDAExecutionProvider'] + else: + data['gpu'] = -1 + widget_text.write("\n" + base_text + "No NVIDIA GPU detected. Switching to CPU... ") + run_type = ['CPUExecutionProvider'] + else: + run_type = ['CPUExecutionProvider'] + + self.onnx_models[c] = ort.InferenceSession(os.path.join('models/MDX_Net_Models', model_set), providers=run_type) + widget_text.write('Done!\n') + + def prediction(self, m): + #mix, rate = sf.read(m) + mix, rate = librosa.load(m, mono=False, sr=44100) + if mix.ndim == 1: + mix = np.asfortranarray([mix,mix]) + mix = mix.T + sources = self.demix(mix.T) + widget_text.write(base_text + 'Inferences complete!\n') + c = -1 + + #Main Save Path + save_path = os.path.dirname(base_name) + + #Vocal Path + vocal_name = '(Vocals)' + if data['modelFolder']: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_2}_{vocal_name}',) + else: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_2}_{vocal_name}',) + + #Instrumental Path + Instrumental_name = '(Instrumental)' + if data['modelFolder']: + Instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_2}_{Instrumental_name}',) + else: + Instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_2}_{Instrumental_name}',) + + #Non-Reduced Vocal Path + vocal_name = '(Vocals)' + if data['modelFolder']: + non_reduced_vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_2}_{vocal_name}_No_Reduction',) + else: + non_reduced_vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_2}_{vocal_name}_No_Reduction',) + + if os.path.isfile(non_reduced_vocal_path): + file_exists_n = 'there' + else: + file_exists_n = 'not_there' + + if os.path.isfile(vocal_path): + file_exists = 'there' + else: + file_exists = 'not_there' + + if not data['noisereduc_s'] == 'None': + c += 1 + if not data['demucsmodel']: + if data['inst_only'] and not data['voc_only']: + widget_text.write(base_text + 'Preparing to save Instrumental...') + else: + widget_text.write(base_text + 'Saving vocals... ') + sf.write(non_reduced_vocal_path, sources[c].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + widget_text.write(base_text + 'Performing Noise Reduction... ') + reduction_sen = float(int(data['noisereduc_s'])/10) + subprocess.call("lib_v5\\sox\\sox.exe" + ' "' + + f"{str(non_reduced_vocal_path)}" + '" "' + f"{str(vocal_path)}" + '" ' + + "noisered lib_v5\\sox\\mdxnetnoisereduc.prof " + f"{reduction_sen}", + shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, stderr=subprocess.PIPE) + widget_text.write('Done!\n') + update_progress(**progress_kwargs, + step=(0.95)) + else: + if data['inst_only'] and not data['voc_only']: + widget_text.write(base_text + 'Preparing Instrumental...') + else: + widget_text.write(base_text + 'Saving Vocals... ') + sf.write(non_reduced_vocal_path, sources[3].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + widget_text.write(base_text + 'Performing Noise Reduction... ') + reduction_sen = float(int(data['noisereduc_s'])/10) + subprocess.call("lib_v5\\sox\\sox.exe" + ' "' + + f"{str(non_reduced_vocal_path)}" + '" "' + f"{str(vocal_path)}" + '" ' + + "noisered lib_v5\\sox\\mdxnetnoisereduc.prof " + f"{reduction_sen}", + shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, stderr=subprocess.PIPE) + update_progress(**progress_kwargs, + step=(0.95)) + widget_text.write('Done!\n') + else: + c += 1 + if not data['demucsmodel']: + widget_text.write(base_text + 'Saving Vocals..') + sf.write(vocal_path, sources[c].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + else: + widget_text.write(base_text + 'Saving Vocals... ') + sf.write(vocal_path, sources[3].T, rate) + update_progress(**progress_kwargs, + step=(0.9)) + widget_text.write('Done!\n') + + if data['voc_only'] and not data['inst_only']: + pass + else: + finalfiles = [ + { + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'files':[str(music_file), vocal_path], + } + ] + widget_text.write(base_text + 'Saving Instrumental... ') + for i, e in tqdm(enumerate(finalfiles)): + + wave, specs = {}, {} + + mp = ModelParameters(e['model_params']) + + for i in range(len(e['files'])): + spec = {} + + for d in range(len(mp.param['band']), 0, -1): + bp = mp.param['band'][d] + + if d == len(mp.param['band']): # high-end band + wave[d], _ = librosa.load( + e['files'][i], bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if len(wave[d].shape) == 1: # mono to stereo + wave[d] = np.array([wave[d], wave[d]]) + else: # lower bands + wave[d] = librosa.resample(wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + spec[d] = spec_utils.wave_to_spectrogram(wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], mp.param['mid_side_b2'], mp.param['reverse']) + + specs[i] = spec_utils.combine_spectrograms(spec, mp) + + del wave + + ln = min([specs[0].shape[2], specs[1].shape[2]]) + specs[0] = specs[0][:,:,:ln] + specs[1] = specs[1][:,:,:ln] + X_mag = np.abs(specs[0]) + y_mag = np.abs(specs[1]) + max_mag = np.where(X_mag >= y_mag, X_mag, y_mag) + v_spec = specs[1] - max_mag * np.exp(1.j * np.angle(specs[0])) + update_progress(**progress_kwargs, + step=(1)) + sf.write(Instrumental_path, spec_utils.cmb_spectrogram_to_wave(-v_spec, mp), mp.param['sr']) + if data['inst_only']: + if file_exists == 'there': + pass + else: + try: + os.remove(vocal_path) + except: + pass + + widget_text.write('Done!\n') + + if data['noisereduc_s'] == 'None': + pass + elif data['inst_only']: + if file_exists_n == 'there': + pass + else: + try: + os.remove(non_reduced_vocal_path) + except: + pass + else: + try: + os.remove(non_reduced_vocal_path) + except: + pass + + widget_text.write(base_text + 'Completed Seperation!\n\n') + + def demix(self, mix): + # 1 = demucs only + # 0 = onnx only + if data['chunks'] == 'Full': + chunk_set = 0 + else: + chunk_set = data['chunks'] + + if data['chunks'] == 'Auto': + if data['gpu'] == 0: + try: + gpu_mem = round(torch.cuda.get_device_properties(0).total_memory/1.074e+9) + except: + widget_text.write(base_text + 'NVIDIA GPU Required for conversion!\n') + if int(gpu_mem) <= int(5): + chunk_set = int(5) + widget_text.write(base_text + 'Chunk size auto-set to 5... \n') + if gpu_mem in [6, 7]: + chunk_set = int(30) + widget_text.write(base_text + 'Chunk size auto-set to 30... \n') + if gpu_mem in [8, 9, 10, 11, 12, 13, 14, 15]: + chunk_set = int(40) + widget_text.write(base_text + 'Chunk size auto-set to 40... \n') + if int(gpu_mem) >= int(16): + chunk_set = int(60) + widget_text.write(base_text + 'Chunk size auto-set to 60... \n') + if data['gpu'] == -1: + sys_mem = psutil.virtual_memory().total >> 30 + if int(sys_mem) <= int(4): + chunk_set = int(1) + widget_text.write(base_text + 'Chunk size auto-set to 1... \n') + if sys_mem in [5, 6, 7, 8]: + chunk_set = int(10) + widget_text.write(base_text + 'Chunk size auto-set to 10... \n') + if sys_mem in [9, 10, 11, 12, 13, 14, 15, 16]: + chunk_set = int(25) + widget_text.write(base_text + 'Chunk size auto-set to 25... \n') + if int(sys_mem) >= int(17): + chunk_set = int(60) + widget_text.write(base_text + 'Chunk size auto-set to 60... \n') + elif data['chunks'] == 'Full': + chunk_set = 0 + widget_text.write(base_text + "Chunk size set to full... \n") + else: + chunk_set = int(data['chunks']) + widget_text.write(base_text + "Chunk size user-set to "f"{chunk_set}... \n") + + samples = mix.shape[-1] + margin = margin_set + chunk_size = chunk_set*44100 + assert not margin == 0, 'margin cannot be zero!' + if margin > chunk_size: + margin = chunk_size + + b = np.array([[[0.5]], [[0.5]], [[0.7]], [[0.9]]]) + segmented_mix = {} + + if chunk_set == 0 or samples < chunk_size: + chunk_size = samples + + counter = -1 + for skip in range(0, samples, chunk_size): + counter+=1 + + s_margin = 0 if counter == 0 else margin + end = min(skip+chunk_size+margin, samples) + + start = skip-s_margin + + segmented_mix[skip] = mix[:,start:end].copy() + if end == samples: + break + + if not data['demucsmodel']: + sources = self.demix_base(segmented_mix, margin_size=margin) + + else: # both, apply spec effects + base_out = self.demix_base(segmented_mix, margin_size=margin) + demucs_out = self.demix_demucs(segmented_mix, margin_size=margin) + nan_count = np.count_nonzero(np.isnan(demucs_out)) + np.count_nonzero(np.isnan(base_out)) + if nan_count > 0: + print('Warning: there are {} nan values in the array(s).'.format(nan_count)) + demucs_out, base_out = np.nan_to_num(demucs_out), np.nan_to_num(base_out) + sources = {} + + sources[3] = (spec_effects(wave=[demucs_out[3],base_out[0]], + algorithm='default', + value=b[3])*1.03597672895) # compensation + return sources + + def demix_base(self, mixes, margin_size): + chunked_sources = [] + onnxitera = len(mixes) + onnxitera_calc = onnxitera * 2 + gui_progress_bar_onnx = 0 + widget_text.write(base_text + "Running ONNX Inference...\n") + widget_text.write(base_text + "Processing "f"{onnxitera} slices... ") + print(' Running ONNX Inference...') + for mix in mixes: + gui_progress_bar_onnx += 1 + if data['demucsmodel']: + update_progress(**progress_kwargs, + step=(0.1 + (0.5/onnxitera_calc * gui_progress_bar_onnx))) + else: + update_progress(**progress_kwargs, + step=(0.1 + (0.9/onnxitera * gui_progress_bar_onnx))) + cmix = mixes[mix] + sources = [] + n_sample = cmix.shape[1] + + mod = 0 + for model in self.models: + mod += 1 + trim = model.n_fft//2 + gen_size = model.chunk_size-2*trim + pad = gen_size - n_sample%gen_size + mix_p = np.concatenate((np.zeros((2,trim)), cmix, np.zeros((2,pad)), np.zeros((2,trim))), 1) + mix_waves = [] + i = 0 + while i < n_sample + pad: + waves = np.array(mix_p[:, i:i+model.chunk_size]) + mix_waves.append(waves) + i += gen_size + mix_waves = torch.tensor(mix_waves, dtype=torch.float32).to(cpu) + with torch.no_grad(): + _ort = self.onnx_models[mod] + spek = model.stft(mix_waves) + + tar_waves = model.istft(torch.tensor(_ort.run(None, {'input': spek.cpu().numpy()})[0]))#.cpu() + + tar_signal = tar_waves[:,:,trim:-trim].transpose(0,1).reshape(2, -1).numpy()[:, :-pad] + + start = 0 if mix == 0 else margin_size + end = None if mix == list(mixes.keys())[::-1][0] else -margin_size + if margin_size == 0: + end = None + sources.append(tar_signal[:,start:end]) + + + chunked_sources.append(sources) + _sources = np.concatenate(chunked_sources, axis=-1) + del self.onnx_models + widget_text.write('Done!\n') + return _sources + + def demix_demucs(self, mix, margin_size): + processed = {} + demucsitera = len(mix) + demucsitera_calc = demucsitera * 2 + gui_progress_bar_demucs = 0 + widget_text.write(base_text + "Running Demucs Inference...\n") + widget_text.write(base_text + "Processing "f"{len(mix)} slices... ") + print(' Running Demucs Inference...') + for nmix in mix: + gui_progress_bar_demucs += 1 + update_progress(**progress_kwargs, + step=(0.35 + (1.05/demucsitera_calc * gui_progress_bar_demucs))) + cmix = mix[nmix] + cmix = torch.tensor(cmix, dtype=torch.float32) + ref = cmix.mean(0) + cmix = (cmix - ref.mean()) / ref.std() + shift_set = 0 + with torch.no_grad(): + sources = apply_model(self.demucs, cmix.to(device), split=True, overlap=overlap_set, shifts=shift_set) + sources = (sources * ref.std() + ref.mean()).cpu().numpy() + sources[[0,1]] = sources[[1,0]] + + start = 0 if nmix == 0 else margin_size + end = None if nmix == list(mix.keys())[::-1][0] else -margin_size + if margin_size == 0: + end = None + processed[nmix] = sources[:,:,start:end].copy() + + sources = list(processed.values()) + sources = np.concatenate(sources, axis=-1) + widget_text.write('Done!\n') + return sources + + +def update_progress(progress_var, total_files, file_num, step: float = 1): + """Calculate the progress for the progress widget in the GUI""" + base = (100 / total_files) + progress = base * (file_num - 1) + progress += base * step + + progress_var.set(progress) + +def get_baseText(total_files, file_num): + """Create the base text for the command widget""" + text = 'File {file_num}/{total_files} '.format(file_num=file_num, + total_files=total_files) + return text + +warnings.filterwarnings("ignore") +cpu = torch.device('cpu') +device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') + +def hide_opt(): + with open(os.devnull, "w") as devnull: + old_stdout = sys.stdout + sys.stdout = devnull + try: + yield + finally: + sys.stdout = old_stdout + +class VocalRemover(object): + + def __init__(self, data, text_widget: tk.Text): + self.data = data + self.text_widget = text_widget + self.models = defaultdict(lambda: None) + self.devices = defaultdict(lambda: None) + # self.offset = model.offset + + + +def update_progress(progress_var, total_files, file_num, step: float = 1): + """Calculate the progress for the progress widget in the GUI""" + base = (100 / total_files) + progress = base * (file_num - 1) + progress += base * step + + progress_var.set(progress) + +def get_baseText(total_files, file_num): + """Create the base text for the command widget""" + text = 'File {file_num}/{total_files} '.format(file_num=file_num, + total_files=total_files) + return text + +def determineModelFolderName(): + """ + Determine the name that is used for the folder and appended + to the back of the music files + """ + modelFolderName = '' + if not data['modelFolder']: + # Model Test Mode not selected + return modelFolderName + + # -Instrumental- + if os.path.isfile(data['instrumentalModel']): + modelFolderName += os.path.splitext(os.path.basename(data['instrumentalModel']))[0] + + if modelFolderName: + modelFolderName = '/' + modelFolderName + + return modelFolderName + +class VocalRemover(object): + + def __init__(self, data, text_widget: tk.Text): + self.data = data + self.text_widget = text_widget + # self.offset = model.offset + +data = { + # Paths + 'input_paths': None, + 'export_path': None, + 'saveFormat': 'wav', + # Processing Options + 'gpu': -1, + 'postprocess': True, + 'tta': True, + 'output_image': True, + 'voc_only': False, + 'inst_only': False, + 'demucsmodel': True, + 'gpu': -1, + 'chunks': 'auto', + 'non_red': False, + 'noisereduc_s': 3, + 'mixing': 'default', + 'ensChoose': 'HP1 Models', + 'algo': 'Instrumentals (Min Spec)', + # Models + 'instrumentalModel': None, + 'useModel': None, + # Constants + 'window_size': 512, + 'agg': 10, + 'high_end_process': 'mirroring' +} + +default_window_size = data['window_size'] +default_agg = data['agg'] +default_chunks = data['chunks'] +default_noisereduc_s = data['noisereduc_s'] + + +def update_progress(progress_var, total_files, file_num, step: float = 1): + """Calculate the progress for the progress widget in the GUI""" + base = (100 / total_files) + progress = base * (file_num - 1) + progress += base * step + + progress_var.set(progress) + +def get_baseText(total_files, file_num): + """Create the base text for the command widget""" + text = 'File {file_num}/{total_files} '.format(file_num=file_num, + total_files=total_files) + return text + +def main(window: tk.Wm, text_widget: tk.Text, button_widget: tk.Button, progress_var: tk.Variable, + **kwargs: dict): + + global widget_text + global gui_progress_bar + global music_file + global channel_set + global margin_set + global overlap_set + global default_chunks + global default_noisereduc_s + global base_name + global progress_kwargs + global base_text + global model_set + global model_set_name + global ModelName_2 + + model_set = 'UVR_MDXNET_9703.onnx' + model_set_name = 'UVR_MDXNET_9703' + + # Update default settings + default_chunks = data['chunks'] + default_noisereduc_s = data['noisereduc_s'] + + channel_set = int(64) + margin_set = int(44100) + overlap_set = float(0.5) + + widget_text = text_widget + gui_progress_bar = progress_var + + onnxmissing = "[ONNXRuntimeError] : 3 : NO_SUCHFILE" + runtimeerr = "CUDNN error executing cudnnSetTensorNdDescriptor" + cuda_err = "CUDA out of memory" + mod_err = "ModuleNotFoundError" + file_err = "FileNotFoundError" + ffmp_err = """audioread\__init__.py", line 116, in audio_open""" + sf_write_err = "sf.write" + + try: + with open('errorlog.txt', 'w') as f: + f.write(f'No errors to report at this time.' + f'\n\nLast Process Method Used: Ensemble Mode' + + f'\nLast Conversion Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + global nn_arch_sizes + global nn_architecture + + nn_arch_sizes = [ + 31191, # default + 33966, 123821, 123812, 537238, 537227 # custom + ] + + def save_files(wav_instrument, wav_vocals): + """Save output music files""" + vocal_name = '(Vocals)' + instrumental_name = '(Instrumental)' + save_path = os.path.dirname(base_name) + + # Swap names if vocal model + + VModel="Vocal" + + if VModel in model_name: + # Reverse names + vocal_name, instrumental_name = instrumental_name, vocal_name + + # Save Temp File + # For instrumental the instrumental is the temp file + # and for vocal the instrumental is the temp file due + # to reversement + + sf.write(f'temp.wav', + wav_instrument, mp.param['sr']) + + # -Save files- + # Instrumental + if instrumental_name is not None: + instrumental_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name = f'{os.path.basename(base_name)}_{ModelName_1}_{instrumental_name}', + ) + + if VModel in ModelName_1 and data['voc_only']: + sf.write(instrumental_path, + wav_instrument, mp.param['sr']) + elif VModel in ModelName_1 and data['inst_only']: + pass + elif data['voc_only']: + pass + else: + sf.write(instrumental_path, + wav_instrument, mp.param['sr']) + + # Vocal + if vocal_name is not None: + vocal_path = '{save_path}/{file_name}.wav'.format( + save_path=save_path, + file_name=f'{os.path.basename(base_name)}_{ModelName_1}_{vocal_name}', + ) + + if VModel in ModelName_1 and data['inst_only']: + sf.write(vocal_path, + wav_vocals, mp.param['sr']) + elif VModel in ModelName_1 and data['voc_only']: + pass + elif data['inst_only']: + pass + else: + sf.write(vocal_path, + wav_vocals, mp.param['sr']) + + data.update(kwargs) + + # Update default settings + global default_window_size + global default_agg + default_window_size = data['window_size'] + default_agg = data['agg'] + + stime = time.perf_counter() + progress_var.set(0) + text_widget.clear() + button_widget.configure(state=tk.DISABLED) # Disable Button + + if os.path.exists('models/Main_Models/7_HP2-UVR.pth') \ + or os.path.exists('models/Main_Models/8_HP2-UVR.pth') \ + or os.path.exists('models/Main_Models/9_HP2-UVR.pth'): + hp2_ens = 'on' + else: + hp2_ens = 'off' + + print('Do all of the HP models exist? ' + hp2_ens) + + # Separation Preperation + try: #Ensemble Dictionary + + if not data['ensChoose'] == 'User Ensemble': + HP1_Models = [ + { + 'model_name':'1_HP-UVR', + 'model_name_c':'1st HP Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/1_HP-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 1/2' + }, + { + 'model_name':'2_HP-UVR', + 'model_name_c':'2nd HP Model', + 'model_params':'lib_v5/modelparams/4band_v2.json', + 'model_param_name':'4band_v2', + 'model_location':'models/Main_Models/2_HP-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 2/2' + } + ] + + HP2_Models = [ + { + 'model_name':'7_HP2-UVR', + 'model_name_c':'1st HP2 Model', + 'model_params':'lib_v5/modelparams/3band_44100_msb2.json', + 'model_param_name':'3band_44100_msb2', + 'model_location':'models/Main_Models/7_HP2-UVR.pth', + 'using_archtecture': '537238KB', + 'loop_name': 'Ensemble Mode - Model 1/3' + }, + { + 'model_name':'8_HP2-UVR', + 'model_name_c':'2nd HP2 Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/8_HP2-UVR.pth', + 'using_archtecture': '537238KB', + 'loop_name': 'Ensemble Mode - Model 2/3' + }, + { + 'model_name':'9_HP2-UVR', + 'model_name_c':'3rd HP2 Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/9_HP2-UVR.pth', + 'using_archtecture': '537238KB', + 'loop_name': 'Ensemble Mode - Model 3/3' + } + ] + + All_HP_Models = [ + { + 'model_name':'7_HP2-UVR', + 'model_name_c':'1st HP2 Model', + 'model_params':'lib_v5/modelparams/3band_44100_msb2.json', + 'model_param_name':'3band_44100_msb2', + 'model_location':'models/Main_Models/7_HP2-UVR.pth', + 'using_archtecture': '537238KB', + 'loop_name': 'Ensemble Mode - Model 1/5' + + }, + { + 'model_name':'8_HP2-UVR', + 'model_name_c':'2nd HP2 Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/8_HP2-UVR.pth', + 'using_archtecture': '537238KB', + 'loop_name': 'Ensemble Mode - Model 2/5' + + }, + { + 'model_name':'9_HP2-UVR', + 'model_name_c':'3rd HP2 Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/9_HP2-UVR.pth', + 'using_archtecture': '537238KB', + 'loop_name': 'Ensemble Mode - Model 3/5' + }, + { + 'model_name':'1_HP-UVR', + 'model_name_c':'1st HP Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/1_HP-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 4/5' + }, + { + 'model_name':'2_HP-UVR', + 'model_name_c':'2nd HP Model', + 'model_params':'lib_v5/modelparams/4band_v2.json', + 'model_param_name':'4band_v2', + 'model_location':'models/Main_Models/2_HP-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 5/5' + } + ] + + Vocal_Models = [ + { + 'model_name':'3_HP-Vocal-UVR', + 'model_name_c':'1st Vocal Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/3_HP-Vocal-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 1/2' + }, + { + 'model_name':'4_HP-Vocal-UVR', + 'model_name_c':'2nd Vocal Model', + 'model_params':'lib_v5/modelparams/4band_44100.json', + 'model_param_name':'4band_44100', + 'model_location':'models/Main_Models/4_HP-Vocal-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 2/2' + } + ] + + mdx_vr = [ + { + 'model_name':'VR_Model', + 'mdx_model_name': 'UVR_MDXNET_9703', + 'model_name_c':'VR Model', + 'model_params':'lib_v5/modelparams/4band_v2.json', + 'model_param_name':'4band_v2', + 'model_location':'models/Main_Models/2_HP-UVR.pth', + 'using_archtecture': '123821KB', + 'loop_name': 'Ensemble Mode - Model 1/2' + } + ] + + if data['ensChoose'] == 'HP Models': + loops = HP1_Models + ensefolder = 'HP_Models_Ensemble_Outputs' + ensemode = 'HP_Models' + if data['ensChoose'] == 'HP2 Models': + loops = HP2_Models + ensefolder = 'HP2_Models_Ensemble_Outputs' + ensemode = 'HP2_Models' + if data['ensChoose'] == 'All HP/HP2 Models': + loops = All_HP_Models + ensefolder = 'All_HP_HP2_Models_Ensemble_Outputs' + ensemode = 'All_HP_HP2_Models' + if data['ensChoose'] == 'Vocal Models': + loops = Vocal_Models + ensefolder = 'Vocal_Models_Ensemble_Outputs' + ensemode = 'Vocal_Models' + if data['ensChoose'] == 'MDX-Net/VR Ensemble': + loops = mdx_vr + ensefolder = 'MDX_VR_Ensemble_Outputs' + ensemode = 'MDX-Net_VR' + + + #Prepare Audiofile(s) + for file_num, music_file in enumerate(data['input_paths'], start=1): + print(data['input_paths']) + # -Get text and update progress- + base_text = get_baseText(total_files=len(data['input_paths']), + file_num=file_num) + progress_kwargs = {'progress_var': progress_var, + 'total_files': len(data['input_paths']), + 'file_num': file_num} + update_progress(**progress_kwargs, + step=0) + + try: + total, used, free = shutil.disk_usage("/") + + total_space = int(total/1.074e+9) + used_space = int(used/1.074e+9) + free_space = int(free/1.074e+9) + + if int(free/1.074e+9) <= int(2): + text_widget.write('Error: Not enough storage on main drive to continue. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly. \n\nPlease ensure your main drive has at least 3 GB\'s of storage and try again.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n') + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if int(free/1.074e+9) in [3, 4, 5, 6, 7, 8]: + text_widget.write('Warning: Your main drive is running low on storage. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n\n') + except: + pass + + + #Prepare to loop models + for i, c in tqdm(enumerate(loops), disable=True, desc='Iterations..'): + + if hp2_ens == 'off' and loops == HP2_Models: + text_widget.write(base_text + 'You must install the UVR expansion pack in order to use this ensemble.\n') + text_widget.write(base_text + 'Please install the expansion pack or choose another ensemble.\n') + text_widget.write(base_text + 'See the \"Updates\" tab in the Help Guide for installation instructions.\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') # nopep8 + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) + return + elif hp2_ens == 'off' and loops == All_HP_Models: + text_widget.write(base_text + 'You must install the UVR expansion pack in order to use this ensemble.\n') + text_widget.write(base_text + 'Please install the expansion pack or choose another ensemble.\n') + text_widget.write(base_text + 'See the \"Updates\" tab in the Help Guide for installation instructions.\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') # nopep8 + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) + return + + presentmodel = Path(c['model_location']) + + if presentmodel.is_file(): + print(f'The file {presentmodel} exist') + else: + text_widget.write(base_text + 'Model "' + c['model_name'] + '.pth" is missing, moving to next... \n\n') + continue + + text_widget.write(c['loop_name'] + '\n\n') + + text_widget.write(base_text + 'Loading ' + c['model_name_c'] + '... ') + + aggresive_set = float(data['agg']/100) + + model_size = math.ceil(os.stat(c['model_location']).st_size / 1024) + nn_architecture = '{}KB'.format(min(nn_arch_sizes, key=lambda x:abs(x-model_size))) + + nets = importlib.import_module('lib_v5.nets' + f'_{nn_architecture}'.replace('_{}KB'.format(nn_arch_sizes[0]), ''), package=None) + + text_widget.write('Done!\n') + + ModelName=(c['model_location']) + + #Package Models + + model_hash = hashlib.md5(open(ModelName,'rb').read()).hexdigest() + print(model_hash) + + #v5 Models + + if model_hash == '47939caf0cfe52a0e81442b85b971dfd': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == '4e4ecb9764c50a8c414fee6e10395bbe': + model_params_d=str('lib_v5/modelparams/4band_v2.json') + param_name=str('4band_v2') + if model_hash == 'e60a1e84803ce4efc0a6551206cc4b71': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == 'a82f14e75892e55e994376edbf0c8435': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == '6dd9eaa6f0420af9f1d403aaafa4cc06': + model_params_d=str('lib_v5/modelparams/4band_v2_sn.json') + param_name=str('4band_v2_sn') + if model_hash == '5c7bbca45a187e81abbbd351606164e5': + model_params_d=str('lib_v5/modelparams/3band_44100_msb2.json') + param_name=str('3band_44100_msb2') + if model_hash == 'd6b2cb685a058a091e5e7098192d3233': + model_params_d=str('lib_v5/modelparams/3band_44100_msb2.json') + param_name=str('3band_44100_msb2') + if model_hash == 'c1b9f38170a7c90e96f027992eb7c62b': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + if model_hash == 'c3448ec923fa0edf3d03a19e633faa53': + model_params_d=str('lib_v5/modelparams/4band_44100.json') + param_name=str('4band_44100') + + #v4 Models + + if model_hash == '6a00461c51c2920fd68937d4609ed6c8': + model_params_d=str('lib_v5/modelparams/1band_sr16000_hl512.json') + param_name=str('1band_sr16000_hl512') + if model_hash == '0ab504864d20f1bd378fe9c81ef37140': + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if model_hash == '7dd21065bf91c10f7fccb57d7d83b07f': + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if model_hash == '80ab74d65e515caa3622728d2de07d23': + model_params_d=str('lib_v5/modelparams/1band_sr32000_hl512.json') + param_name=str('1band_sr32000_hl512') + if model_hash == 'edc115e7fc523245062200c00caa847f': + model_params_d=str('lib_v5/modelparams/1band_sr33075_hl384.json') + param_name=str('1band_sr33075_hl384') + if model_hash == '28063e9f6ab5b341c5f6d3c67f2045b7': + model_params_d=str('lib_v5/modelparams/1band_sr33075_hl384.json') + param_name=str('1band_sr33075_hl384') + if model_hash == 'b58090534c52cbc3e9b5104bad666ef2': + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl512.json') + param_name=str('1band_sr44100_hl512') + if model_hash == '0cdab9947f1b0928705f518f3c78ea8f': + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl512.json') + param_name=str('1band_sr44100_hl512') + if model_hash == 'ae702fed0238afb5346db8356fe25f13': + model_params_d=str('lib_v5/modelparams/1band_sr44100_hl1024.json') + param_name=str('1band_sr44100_hl1024') + + def determineenseFolderName(): + """ + Determine the name that is used for the folder and appended + to the back of the music files + """ + enseFolderName = '' + + if str(ensefolder): + enseFolderName += os.path.splitext(os.path.basename(ensefolder))[0] + + if enseFolderName: + enseFolderName = '/' + enseFolderName + + return enseFolderName + + enseFolderName = determineenseFolderName() + if enseFolderName: + folder_path = f'{data["export_path"]}{enseFolderName}' + if not os.path.isdir(folder_path): + os.mkdir(folder_path) + + # Determine File Name + base_name = f'{data["export_path"]}{enseFolderName}/{file_num}_{os.path.splitext(os.path.basename(music_file))[0]}' + enseExport = f'{data["export_path"]}{enseFolderName}/' + trackname = f'{file_num}_{os.path.splitext(os.path.basename(music_file))[0]}' + + ModelName_1=(c['model_name']) + + try: + ModelName_2=(c['mdx_model_name']) + except: + pass + + print('Model Parameters:', model_params_d) + text_widget.write(base_text + 'Loading assigned model parameters ' + '\"' + param_name + '\"... ') + + mp = ModelParameters(model_params_d) + + text_widget.write('Done!\n') + + #Load model + if os.path.isfile(c['model_location']): + device = torch.device('cpu') + model = nets.CascadedASPPNet(mp.param['bins'] * 2) + model.load_state_dict(torch.load(c['model_location'], + map_location=device)) + if torch.cuda.is_available() and data['gpu'] >= 0: + device = torch.device('cuda:{}'.format(data['gpu'])) + model.to(device) + + model_name = os.path.basename(c["model_name"]) + + # -Go through the different steps of seperation- + # Wave source + text_widget.write(base_text + 'Loading audio source... ') + + X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {} + + bands_n = len(mp.param['band']) + + for d in range(bands_n, 0, -1): + bp = mp.param['band'][d] + + if d == bands_n: # high-end band + X_wave[d], _ = librosa.load( + music_file, bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if X_wave[d].ndim == 1: + X_wave[d] = np.asarray([X_wave[d], X_wave[d]]) + else: # lower bands + X_wave[d] = librosa.resample(X_wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + # Stft of wave source + + X_spec_s[d] = spec_utils.wave_to_spectrogram_mt(X_wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], + mp.param['mid_side_b2'], mp.param['reverse']) + + if d == bands_n and data['high_end_process'] != 'none': + input_high_end_h = (bp['n_fft']//2 - bp['crop_stop']) + (mp.param['pre_filter_stop'] - mp.param['pre_filter_start']) + input_high_end = X_spec_s[d][:, bp['n_fft']//2-input_high_end_h:bp['n_fft']//2, :] + + text_widget.write('Done!\n') + + update_progress(**progress_kwargs, + step=0.1) + + text_widget.write(base_text + 'Loading the stft of audio source... ') + text_widget.write('Done!\n') + text_widget.write(base_text + "Please Wait...\n") + + X_spec_m = spec_utils.combine_spectrograms(X_spec_s, mp) + + del X_wave, X_spec_s + + def inference(X_spec, device, model, aggressiveness): + + def _execute(X_mag_pad, roi_size, n_window, device, model, aggressiveness): + model.eval() + + with torch.no_grad(): + preds = [] + + iterations = [n_window] + + total_iterations = sum(iterations) + + text_widget.write(base_text + "Processing "f"{total_iterations} Slices... ") + + for i in tqdm(range(n_window)): + update_progress(**progress_kwargs, + step=(0.1 + (0.8/n_window * i))) + start = i * roi_size + X_mag_window = X_mag_pad[None, :, :, start:start + data['window_size']] + X_mag_window = torch.from_numpy(X_mag_window).to(device) + + pred = model.predict(X_mag_window, aggressiveness) + + pred = pred.detach().cpu().numpy() + preds.append(pred[0]) + + pred = np.concatenate(preds, axis=2) + + text_widget.write('Done!\n') + return pred + + def preprocess(X_spec): + X_mag = np.abs(X_spec) + X_phase = np.angle(X_spec) + + return X_mag, X_phase + + X_mag, X_phase = preprocess(X_spec) + + coef = X_mag.max() + X_mag_pre = X_mag / coef + + n_frame = X_mag_pre.shape[2] + pad_l, pad_r, roi_size = dataset.make_padding(n_frame, + data['window_size'], model.offset) + n_window = int(np.ceil(n_frame / roi_size)) + + X_mag_pad = np.pad( + X_mag_pre, ((0, 0), (0, 0), (pad_l, pad_r)), mode='constant') + + pred = _execute(X_mag_pad, roi_size, n_window, + device, model, aggressiveness) + pred = pred[:, :, :n_frame] + + if data['tta']: + pad_l += roi_size // 2 + pad_r += roi_size // 2 + n_window += 1 + + X_mag_pad = np.pad( + X_mag_pre, ((0, 0), (0, 0), (pad_l, pad_r)), mode='constant') + + pred_tta = _execute(X_mag_pad, roi_size, n_window, + device, model, aggressiveness) + pred_tta = pred_tta[:, :, roi_size // 2:] + pred_tta = pred_tta[:, :, :n_frame] + + return (pred + pred_tta) * 0.5 * coef, X_mag, np.exp(1.j * X_phase) + else: + return pred * coef, X_mag, np.exp(1.j * X_phase) + + aggressiveness = {'value': aggresive_set, 'split_bin': mp.param['band'][1]['crop_stop']} + + if data['tta']: + text_widget.write(base_text + "Running Inferences (TTA)... \n") + else: + text_widget.write(base_text + "Running Inference... \n") + + pred, X_mag, X_phase = inference(X_spec_m, + device, + model, aggressiveness) + + update_progress(**progress_kwargs, + step=0.85) + + # Postprocess + if data['postprocess']: + try: + text_widget.write(base_text + 'Post processing...') + pred_inv = np.clip(X_mag - pred, 0, np.inf) + pred = spec_utils.mask_silence(pred, pred_inv) + text_widget.write(' Done!\n') + except Exception as e: + text_widget.write('\n' + base_text + 'Post process failed, check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to run Post Processing on "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + # Inverse stft + # nopep8 + y_spec_m = pred * X_phase + v_spec_m = X_spec_m - y_spec_m + + if data['voc_only']: + pass + else: + text_widget.write(base_text + 'Saving Instrumental... ') + + if data['high_end_process'].startswith('mirroring'): + input_high_end_ = spec_utils.mirroring(data['high_end_process'], y_spec_m, input_high_end, mp) + wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, mp, input_high_end_h, input_high_end_) + if data['voc_only']: + pass + else: + text_widget.write('Done!\n') + else: + wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, mp) + if data['voc_only']: + pass + else: + text_widget.write('Done!\n') + + if data['inst_only']: + pass + else: + text_widget.write(base_text + 'Saving Vocals... ') + + if data['high_end_process'].startswith('mirroring'): + input_high_end_ = spec_utils.mirroring(data['high_end_process'], v_spec_m, input_high_end, mp) + + wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, mp, input_high_end_h, input_high_end_) + if data['inst_only']: + pass + else: + text_widget.write('Done!\n') + else: + wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, mp) + if data['inst_only']: + pass + else: + text_widget.write('Done!\n') + + + update_progress(**progress_kwargs, + step=0.9) + + # Save output music files + save_files(wav_instrument, wav_vocals) + + # Save output image + if data['output_image']: + with open('{}_Instruments.jpg'.format(base_name), mode='wb') as f: + image = spec_utils.spectrogram_to_image(y_spec_m) + _, bin_image = cv2.imencode('.jpg', image) + bin_image.tofile(f) + with open('{}_Vocals.jpg'.format(base_name), mode='wb') as f: + image = spec_utils.spectrogram_to_image(v_spec_m) + _, bin_image = cv2.imencode('.jpg', image) + bin_image.tofile(f) + + text_widget.write(base_text + 'Completed Seperation!\n\n') + + + if data['ensChoose'] == 'MDX-Net/VR Ensemble': + text_widget.write('Ensemble Mode - Model 2/2\n\n') + + update_progress(**progress_kwargs, + step=0) + + if data['noisereduc_s'] == 'None': + pass + else: + if not os.path.isfile("lib_v5\sox\sox.exe"): + data['noisereduc_s'] = 'None' + data['non_red'] = False + widget_text.write(base_text + 'SoX is missing and required for noise reduction.\n') + widget_text.write(base_text + 'See the \"More Info\" tab in the Help Guide.\n') + widget_text.write(base_text + 'Noise Reduction will be disabled until SoX is available.\n\n') + + e = os.path.join(data["export_path"]) + + demucsmodel = 'models/Demucs_Model/demucs_extra-3646af93_org.th' + + pred = Predictor() + pred.prediction_setup(demucs_name=demucsmodel, + channels=channel_set) + + # split + pred.prediction( + m=music_file, + ) + else: + pass + + + # Emsembling Outputs + def get_files(folder="", prefix="", suffix=""): + return [f"{folder}{i}" for i in os.listdir(folder) if i.startswith(prefix) if i.endswith(suffix)] + + voc_inst = [ + { + 'algorithm':'min_mag', + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'files':get_files(folder=enseExport, prefix=trackname, suffix="_(Instrumental).wav"), + 'output':'{}_Ensembled_{}_(Instrumental)'.format(trackname, ensemode), + 'type': 'Instrumentals' + }, + { + 'algorithm':'max_mag', + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'files':get_files(folder=enseExport, prefix=trackname, suffix="_(Vocals).wav"), + 'output': '{}_Ensembled_{}_(Vocals)'.format(trackname, ensemode), + 'type': 'Vocals' + } + ] + + inst = [ + { + 'algorithm':'min_mag', + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'files':get_files(folder=enseExport, prefix=trackname, suffix="_(Instrumental).wav"), + 'output':'{}_Ensembled_{}_(Instrumental)'.format(trackname, ensemode), + 'type': 'Instrumentals' + } + ] + + vocal = [ + { + 'algorithm':'max_mag', + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'files':get_files(folder=enseExport, prefix=trackname, suffix="_(Vocals).wav"), + 'output': '{}_Ensembled_{}_(Vocals)'.format(trackname, ensemode), + 'type': 'Vocals' + } + ] + + if data['voc_only']: + ensembles = vocal + elif data['inst_only']: + ensembles = inst + else: + ensembles = voc_inst + + try: + for i, e in tqdm(enumerate(ensembles), desc="Ensembling..."): + + text_widget.write(base_text + "Ensembling " + e['type'] + "... ") + + wave, specs = {}, {} + + mp = ModelParameters(e['model_params']) + + for i in range(len(e['files'])): + + spec = {} + + for d in range(len(mp.param['band']), 0, -1): + bp = mp.param['band'][d] + + if d == len(mp.param['band']): # high-end band + wave[d], _ = librosa.load( + e['files'][i], bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if len(wave[d].shape) == 1: # mono to stereo + wave[d] = np.array([wave[d], wave[d]]) + else: # lower bands + wave[d] = librosa.resample(wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + spec[d] = spec_utils.wave_to_spectrogram(wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], mp.param['mid_side_b2'], mp.param['reverse']) + + specs[i] = spec_utils.combine_spectrograms(spec, mp) + + del wave + + sf.write(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])), + spec_utils.cmb_spectrogram_to_wave(spec_utils.ensembling(e['algorithm'], + specs), mp), mp.param['sr']) + + + if data['saveFormat'] == 'Mp3': + try: + musfile = pydub.AudioSegment.from_wav(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output']))) + musfile.export((os.path.join('{}'.format(data['export_path']),'{}.mp3'.format(e['output']))), format="mp3", bitrate="320k") + os.remove((os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])))) + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Mp3(s).\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on... ') + else: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Mp3(s).\n') + text_widget.write(base_text + 'Please check error log.\n') + text_widget.write(base_text + 'Moving on... ') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to save file as mp3 "{os.path.basename(music_file)}".\n\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + if data['saveFormat'] == 'Flac': + try: + musfile = pydub.AudioSegment.from_wav(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output']))) + musfile.export((os.path.join('{}'.format(data['export_path']),'{}.flac'.format(e['output']))), format="flac") + os.remove((os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])))) + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Flac(s).\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on... ') + else: + text_widget.write(base_text + 'Failed to save output(s) as Flac(s).\n') + text_widget.write(base_text + 'Please check error log.\n') + text_widget.write(base_text + 'Moving on... ') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to save file as flac "{os.path.basename(music_file)}".\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + text_widget.write("Done!\n") + except: + text_widget.write('\n' + base_text + 'Not enough files to ensemble.') + pass + + update_progress(**progress_kwargs, + step=0.95) + text_widget.write("\n") + + try: + if not data['save']: # Deletes all outputs if Save All Outputs isn't checked + files = get_files(folder=enseExport, prefix=trackname, suffix="_(Vocals).wav") + for file in files: + os.remove(file) + if not data['save']: + files = get_files(folder=enseExport, prefix=trackname, suffix="_(Instrumental).wav") + for file in files: + os.remove(file) + except: + pass + + if data['save'] and data['saveFormat'] == 'Mp3': + try: + text_widget.write(base_text + 'Saving all ensemble outputs in Mp3... ') + path = enseExport + #Change working directory + os.chdir(path) + audio_files = os.listdir() + for file in audio_files: + #spliting the file into the name and the extension + name, ext = os.path.splitext(file) + if ext == ".wav": + if trackname in file: + musfile = pydub.AudioSegment.from_wav(file) + #rename them using the old name + ".wav" + musfile.export("{0}.mp3".format(name), format="mp3", bitrate="320k") + try: + files = get_files(folder=enseExport, prefix=trackname, suffix="_(Vocals).wav") + for file in files: + os.remove(file) + except: + pass + try: + files = get_files(folder=enseExport, prefix=trackname, suffix="_(Instrumental).wav") + for file in files: + os.remove(file) + except: + pass + + text_widget.write('Done!\n\n') + base_path = os.path.dirname(os.path.abspath(__file__)) + os.chdir(base_path) + + except Exception as e: + base_path = os.path.dirname(os.path.abspath(__file__)) + os.chdir(base_path) + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Mp3(s).\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + else: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Mp3(s).\n') + text_widget.write(base_text + 'Please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'\nError Received while attempting to save ensembled outputs as mp3s.\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + if data['save'] and data['saveFormat'] == 'Flac': + try: + text_widget.write(base_text + 'Saving all ensemble outputs in Flac... ') + path = enseExport + #Change working directory + os.chdir(path) + audio_files = os.listdir() + for file in audio_files: + #spliting the file into the name and the extension + name, ext = os.path.splitext(file) + if ext == ".wav": + if trackname in file: + musfile = pydub.AudioSegment.from_wav(file) + #rename them using the old name + ".wav" + musfile.export("{0}.flac".format(name), format="flac") + try: + files = get_files(folder=enseExport, prefix=trackname, suffix="_(Vocals).wav") + for file in files: + os.remove(file) + except: + pass + try: + files = get_files(folder=enseExport, prefix=trackname, suffix="_(Instrumental).wav") + for file in files: + os.remove(file) + except: + pass + + text_widget.write('Done!\n\n') + base_path = os.path.dirname(os.path.abspath(__file__)) + os.chdir(base_path) + + except Exception as e: + base_path = os.path.dirname(os.path.abspath(__file__)) + os.chdir(base_path) + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if "ffmpeg" in errmessage: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Flac(s).\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + else: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Flac(s).\n') + text_widget.write(base_text + 'Please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'\nError Received while attempting to ensembled outputs as Flacs.\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + + + try: + os.remove('temp.wav') + except: + pass + + if len(os.listdir(enseExport)) == 0: #Check if the folder is empty + shutil.rmtree(folder_path) #Delete folder if empty + + else: + progress_kwargs = {'progress_var': progress_var, + 'total_files': len(data['input_paths']), + 'file_num': len(data['input_paths'])} + base_text = get_baseText(total_files=len(data['input_paths']), + file_num=len(data['input_paths'])) + + try: + total, used, free = shutil.disk_usage("/") + + total_space = int(total/1.074e+9) + used_space = int(used/1.074e+9) + free_space = int(free/1.074e+9) + + if int(free/1.074e+9) <= int(2): + text_widget.write('Error: Not enough storage on main drive to continue. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly. \n\nPlease ensure your main drive has at least 3 GB\'s of storage and try again.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n') + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if int(free/1.074e+9) in [3, 4, 5, 6, 7, 8]: + text_widget.write('Warning: Your main drive is running low on storage. Your main drive must have \nat least 3 GB\'s of storage in order for this application function properly.\n\n') + text_widget.write('Detected Total Space: ' + str(total_space) + ' GB' + '\n') + text_widget.write('Detected Used Space: ' + str(used_space) + ' GB' + '\n') + text_widget.write('Detected Free Space: ' + str(free_space) + ' GB' + '\n\n') + except: + pass + + music_file = data['input_paths'] + if len(data['input_paths']) <= 1: + text_widget.write(base_text + "Not enough files to process.\n") + pass + else: + update_progress(**progress_kwargs, + step=0.2) + + savefilename = (data['input_paths'][0]) + trackname1 = f'{os.path.splitext(os.path.basename(savefilename))[0]}' + + insts = [ + { + 'algorithm':'min_mag', + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'output':'{}_User_Ensembled_(Min Spec)'.format(trackname1), + 'type': 'Instrumentals' + } + ] + + vocals = [ + { + 'algorithm':'max_mag', + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'output': '{}_User_Ensembled_(Max Spec)'.format(trackname1), + 'type': 'Vocals' + } + ] + + invert_spec = [ + { + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'output': '{}_diff_si'.format(trackname1), + 'type': 'Spectral Inversion' + } + ] + + invert_nor = [ + { + 'model_params':'lib_v5/modelparams/1band_sr44100_hl512.json', + 'output': '{}_diff_ni'.format(trackname1), + 'type': 'Normal Inversion' + } + ] + + if data['algo'] == 'Instrumentals (Min Spec)': + ensem = insts + if data['algo'] == 'Vocals (Max Spec)': + ensem = vocals + if data['algo'] == 'Invert (Spectral)': + ensem = invert_spec + if data['algo'] == 'Invert (Normal)': + ensem = invert_nor + + #Prepare to loop models + if data['algo'] == 'Instrumentals (Min Spec)' or data['algo'] == 'Vocals (Max Spec)': + for i, e in tqdm(enumerate(ensem), desc="Ensembling..."): + text_widget.write(base_text + "Ensembling " + e['type'] + "... ") + + wave, specs = {}, {} + + mp = ModelParameters(e['model_params']) + + for i in range(len(data['input_paths'])): + spec = {} + + for d in range(len(mp.param['band']), 0, -1): + bp = mp.param['band'][d] + + if d == len(mp.param['band']): # high-end band + wave[d], _ = librosa.load( + data['input_paths'][i], bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if len(wave[d].shape) == 1: # mono to stereo + wave[d] = np.array([wave[d], wave[d]]) + else: # lower bands + wave[d] = librosa.resample(wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + spec[d] = spec_utils.wave_to_spectrogram(wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], mp.param['mid_side_b2'], mp.param['reverse']) + + specs[i] = spec_utils.combine_spectrograms(spec, mp) + + del wave + + sf.write(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])), + spec_utils.cmb_spectrogram_to_wave(spec_utils.ensembling(e['algorithm'], + specs), mp), mp.param['sr']) + + if data['saveFormat'] == 'Mp3': + try: + musfile = pydub.AudioSegment.from_wav(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output']))) + musfile.export((os.path.join('{}'.format(data['export_path']),'{}.mp3'.format(e['output']))), format="mp3", bitrate="320k") + os.remove((os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])))) + except Exception as e: + text_widget.write('\n' + base_text + 'Failed to save output(s) as Mp3.') + text_widget.write('\n' + base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + text_widget.write(base_text + f'Complete!\n') + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to run user ensemble:\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) + + return + + if data['saveFormat'] == 'Flac': + try: + musfile = pydub.AudioSegment.from_wav(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output']))) + musfile.export((os.path.join('{}'.format(data['export_path']),'{}.flac'.format(e['output']))), format="flac") + os.remove((os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])))) + except Exception as e: + text_widget.write('\n' + base_text + 'Failed to save output as Flac.\n') + text_widget.write(base_text + 'FFmpeg might be missing or corrupted, please check error log.\n') + text_widget.write(base_text + 'Moving on...\n') + text_widget.write(base_text + f'Complete!\n') + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + errmessage = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while attempting to run user ensemble:\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg might be missing or corrupted.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + errmessage + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) + return + + text_widget.write("Done!\n") + if data['algo'] == 'Invert (Spectral)' and data['algo'] == 'Invert (Normal)': + if len(data['input_paths']) != 2: + text_widget.write(base_text + "Invalid file count.\n") + pass + else: + for i, e in tqdm(enumerate(ensem), desc="Inverting..."): + + wave, specs = {}, {} + + mp = ModelParameters(e['model_params']) + + for i in range(len(data['input_paths'])): + spec = {} + + for d in range(len(mp.param['band']), 0, -1): + bp = mp.param['band'][d] + + if d == len(mp.param['band']): # high-end band + wave[d], _ = librosa.load( + data['input_paths'][i], bp['sr'], False, dtype=np.float32, res_type=bp['res_type']) + + if len(wave[d].shape) == 1: # mono to stereo + wave[d] = np.array([wave[d], wave[d]]) + else: # lower bands + wave[d] = librosa.resample(wave[d+1], mp.param['band'][d+1]['sr'], bp['sr'], res_type=bp['res_type']) + + spec[d] = spec_utils.wave_to_spectrogram(wave[d], bp['hl'], bp['n_fft'], mp.param['mid_side'], mp.param['mid_side_b2'], mp.param['reverse']) + + specs[i] = spec_utils.combine_spectrograms(spec, mp) + + del wave + + ln = min([specs[0].shape[2], specs[1].shape[2]]) + specs[0] = specs[0][:,:,:ln] + specs[1] = specs[1][:,:,:ln] + if data['algo'] == 'Invert (Spectral)': + text_widget.write(base_text + "Performing " + e['type'] + "... ") + X_mag = np.abs(specs[0]) + y_mag = np.abs(specs[1]) + max_mag = np.where(X_mag >= y_mag, X_mag, y_mag) + v_spec = specs[1] - max_mag * np.exp(1.j * np.angle(specs[0])) + sf.write(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])), + spec_utils.cmb_spectrogram_to_wave(-v_spec, mp), mp.param['sr']) + if data['algo'] == 'Invert (Normal)': + v_spec = specs[0] - specs[1] + sf.write(os.path.join('{}'.format(data['export_path']),'{}.wav'.format(e['output'])), + spec_utils.cmb_spectrogram_to_wave(v_spec, mp), mp.param['sr']) + text_widget.write("Done!\n") + + + + except Exception as e: + traceback_text = ''.join(traceback.format_tb(e.__traceback__)) + message = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\n' + if runtimeerr in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Your PC cannot process this audio file with the chunk size selected.\nPlease lower the chunk size and try again.\n\n') + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'Your PC cannot process this audio file with the chunk size selected.\nPlease lower the chunk size and try again.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + f'Raw error details:\n\n' + + message + f'\nError Time Stamp: [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if cuda_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'The application was unable to allocate enough GPU memory to use this model.\n') + text_widget.write(f'Please close any GPU intensive applications and try again.\n') + text_widget.write(f'If the error persists, your GPU might not be supported.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'The application was unable to allocate enough GPU memory to use this model.\n' + + f'Please close any GPU intensive applications and try again.\n' + + f'If the error persists, your GPU might not be supported.\n\n' + + f'Raw error details:\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if mod_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Application files(s) are missing.\n') + text_widget.write("\n" + f'{type(e).__name__} - "{e}"' + "\n\n") + text_widget.write(f'Please check for missing files/scripts in the app directory and try again.\n') + text_widget.write(f'If the error persists, please reinstall application or contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'Application files(s) are missing.\n' + + f'Please check for missing files/scripts in the app directory and try again.\n' + + f'If the error persists, please reinstall application or contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if file_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Missing file error raised.\n') + text_widget.write("\n" + f'{type(e).__name__} - "{e}"' + "\n\n") + text_widget.write("\n" + f'Please address the error and try again.' + "\n") + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'Missing file error raised.\n' + + "\n" + f'Please address the error and try again.' + "\n" + + f'If this error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if ffmp_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'FFmpeg is missing or corrupt.\n') + text_widget.write(f'You will only be able to process .wav files until FFmpeg is available on this system.\n') + text_widget.write(f'See the \"More Info\" tab in the Help Guide.\n\n') + text_widget.write(f'If this error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'FFmpeg is missing or corrupt.\n' + + f'You will only be able to process .wav files until FFmpeg is available on this system.\n' + + f'See the \"More Info\" tab in the Help Guide.\n\n' + + f'If this error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if onnxmissing in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'The application could not detect this MDX-Net model on your system.\n') + text_widget.write(f'Please make sure all the models are present in the correct directory.\n') + text_widget.write(f'If the error persists, please reinstall application or contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'The application could not detect this MDX-Net model on your system.\n' + + f'Please make sure all the models are present in the correct directory.\n' + + f'If the error persists, please reinstall application or contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + if sf_write_err in message: + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n\n') + text_widget.write(f'Could not write audio file.\n') + text_widget.write(f'This could be due to low storage on target device or a system permissions issue.\n') + text_widget.write(f"\nFor raw error details, go to the Error Log tab in the Help Guide.\n") + text_widget.write(f'\nIf the error persists, please contact the developers.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'Could not write audio file.\n' + + f'This could be due to low storage on target device or a system permissions issue.\n' + + f'If the error persists, please contact the developers.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + pass + torch.cuda.empty_cache() + progress_var.set(0) + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + print(traceback_text) + print(type(e).__name__, e) + print(message) + + try: + with open('errorlog.txt', 'w') as f: + f.write(f'Last Error Received:\n\n' + + f'Error Received while processing "{os.path.basename(music_file)}":\n' + + f'Process Method: Ensemble Mode\n\n' + + f'If this error persists, please contact the developers with the error details.\n\n' + + message + f'\nError Time Stamp [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]\n') + except: + tk.messagebox.showerror(master=window, + title='Error Details', + message=message) + progress_var.set(0) + text_widget.write("\n" + base_text + f'Separation failed for the following audio file:\n') + text_widget.write(base_text + f'"{os.path.basename(music_file)}"\n') + text_widget.write(f'\nError Received:\n') + text_widget.write("\nFor raw error details, go to the Error Log tab in the Help Guide.\n") + text_widget.write("\n" + f'Please address the error and try again.' + "\n") + text_widget.write(f'If this error persists, please contact the developers with the error details.\n\n') + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) # Enable Button + return + + update_progress(**progress_kwargs, + step=1) + + + print('Done!') + + progress_var.set(0) + if not data['ensChoose'] == 'User Ensemble': + text_widget.write(base_text + f'Conversions Completed!\n') + elif data['algo'] == 'Instrumentals (Min Spec)' and len(data['input_paths']) <= 1 or data['algo'] == 'Vocals (Max Spec)' and len(data['input_paths']) <= 1: + text_widget.write(base_text + f'Please select 2 or more files to use this feature and try again.\n') + elif data['algo'] == 'Instrumentals (Min Spec)' or data['algo'] == 'Vocals (Max Spec)': + text_widget.write(base_text + f'Ensemble Complete!\n') + elif len(data['input_paths']) != 2 and data['algo'] == 'Invert (Spectral)' or len(data['input_paths']) != 2 and data['algo'] == 'Invert (Normal)': + text_widget.write(base_text + f'Please select exactly 2 files to extract difference.\n') + elif data['algo'] == 'Invert (Spectral)' or data['algo'] == 'Invert (Normal)': + text_widget.write(base_text + f'Complete!\n') + + text_widget.write(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}') # nopep8 + torch.cuda.empty_cache() + button_widget.configure(state=tk.NORMAL) #Enable Button