diff --git a/src/app.py b/src/app.py
index 219bd22..f697841 100644
--- a/src/app.py
+++ b/src/app.py
@@ -57,7 +57,7 @@ class CustomApplication(QtWidgets.QApplication):
# -Create Managers-
self.logger = Logger()
self.settings = QtCore.QSettings(const.APPLICATION_SHORTNAME, const.APPLICATION_NAME)
- #self.settings.clear()
+ # self.settings.clear()
self.resources = ResourcePaths()
self.translator = Translator(self)
self.themeManager = ThemeManager(self)
diff --git a/src/inference/converter.py b/src/inference/converter.py
index 5fc361f..0fb00c7 100644
--- a/src/inference/converter.py
+++ b/src/inference/converter.py
@@ -1,3 +1,33 @@
+# pylint: disable=no-name-in-module, import-error
+# -GUI (Threads)-
+from PySide2 import QtCore # (QRunnable, QThread, QObject, Signal, Slot)
+from PySide2 import QtWidgets
+# Multithreading
+import threading
+from multiprocessing import Pool
+# -Required for conversion-
+import cv2
+import librosa
+import audioread
+import numpy as np
+import soundfile as sf
+import torch
+# -Root imports-
+from .lib import dataset
+from .lib import nets
+from .lib import spec_utils
+from ..resources.resources_manager import (ResourcePaths, Logger)
+# -Other-
+import traceback
+# Loading Bar
+from tqdm import tqdm
+# Timer
+import datetime as dt
+import time
+import os
+# Annotating
+from typing import (Dict, Tuple, Optional, Callable)
+
default_data = {
# Paths
'input_paths': [], # List of paths
@@ -23,3 +53,799 @@ default_data = {
'save_instrumentals': True,
'save_vocals': True,
}
+
+class VocalRemover:
+ def __init__(self, seperation_data: dict, logger: Optional[Logger] = None):
+ # -Universal Data (Same for each file)-
+ self.seperation_data = seperation_data
+ self.general_data = {
+ 'total_loops': None,
+ 'folder_path': None,
+ 'file_add_on': None,
+ }
+ self.logger = logger
+ self.models = {}
+ self.devices = {}
+ # Threads
+ self.all_threads = []
+ # -File Specific Data (Different for each file)-
+ # Updated on every conversion or loop
+ self.loop_data = {
+ # File specific
+ 'file_base_name': None,
+ 'file_num': 0,
+ # Loop specific
+ 'command_base_text': None,
+ 'loop_num': 0,
+ 'progress_step': 0.0,
+ 'music_file': None,
+ 'model_device': {
+ 'model': None,
+ 'device': None,
+ 'model_name': None,
+ },
+ 'constants': {
+ 'sr': None,
+ 'hop_length': None,
+ 'window_size': None,
+ 'n_fft': None,
+ },
+ 'X': None,
+ 'X_mag': None,
+ 'X_phase': None,
+ 'prediction': None,
+ 'sampling_rate': None,
+ 'wav_vocals': None,
+ 'wav_instrument': None,
+ 'y_spec': None,
+ 'v_spec': None,
+ # Spectogram from last seperation
+ 'temp_spectogramm': None,
+ }
+ # Needed for embedded audio player (GUI)
+ self.latest_instrumental_path: str
+ self.latest_vocal_path: str
+
+ def seperate_files(self):
+ """
+ Seperate all files
+ """
+ # Track time
+ stime = time.perf_counter()
+ self._check_for_valid_inputs()
+ self._fill_general_data()
+ self.all_threads = []
+
+ for file_num, file_path in enumerate(self.seperation_data['input_paths'], start=1):
+ if self.seperation_data['multithreading']:
+ thread = threading.Thread(target=self._seperate, args=(file_path, file_num),
+ daemon=True)
+ thread.start()
+ self.all_threads.append(thread)
+ else:
+ self._seperate(file_path,
+ file_num)
+
+ for thread in self.all_threads:
+ thread.join()
+ # Free RAM
+ torch.cuda.empty_cache()
+
+ self.logger.info('Conversion(s) Completed and Saving all Files!')
+ self.logger.info(f'Time Elapsed: {time.strftime("%H:%M:%S", time.gmtime(int(time.perf_counter() - stime)))}')
+
+ def write_to_gui(self, text: Optional[str] = None, include_base_text: bool = True, progress_step: Optional[float] = None):
+ """
+ Update progress and/or write text to the command line
+
+ A new line '\\n' will be automatically appended to the text
+ """
+ self.logger.info(text)
+ print(text)
+
+ def _fill_general_data(self):
+ """
+ Fill the data implemented in general_data
+ """
+ def get_folderPath_fileAddOn() -> Tuple[str, str]:
+ """
+ Get export path and text, whic hwill be appended on the music files name
+ """
+ file_add_on = ''
+ if self.seperation_data['modelFolder']:
+ # Model Test Mode selected
+ # -Instrumental-
+ if os.path.isfile(self.seperation_data['instrumentalModel']):
+ file_add_on += os.path.splitext(os.path.basename(self.seperation_data['instrumentalModel']))[0]
+ # -Vocal-
+ elif os.path.isfile(self.seperation_data['vocalModel']):
+ file_add_on += os.path.splitext(os.path.basename(self.seperation_data['vocalModel']))[0]
+ # -Stack-
+ if os.path.isfile(self.seperation_data['stackModel']):
+ file_add_on += '-' + os.path.splitext(os.path.basename(self.seperation_data['stackModel']))[0]
+
+ # Generate paths
+ folder_path = os.path.join(self.seperation_data['export_path'], file_add_on)
+ file_add_on = f'_{file_add_on}'
+
+ if not os.path.isdir(folder_path):
+ # Folder does not exist
+ os.mkdir(folder_path)
+ else:
+ # Not Model Test Mode selected
+ folder_path = self.seperation_data['export_path']
+
+ return folder_path, file_add_on
+
+ def get_models_devices() -> list:
+ """
+ Return models and devices found
+ """
+ models = {}
+ devices = {}
+
+ # -Instrumental-
+ if os.path.isfile(self.seperation_data['instrumentalModel']):
+ device = torch.device('cpu')
+ model = nets.CascadedASPPNet(self.seperation_data['n_fft'])
+ model.load_state_dict(torch.load(self.seperation_data['instrumentalModel'],
+ map_location=device))
+ if torch.cuda.is_available() and self.seperation_data['gpuConversion']:
+ device = torch.device('cuda:0')
+ model.to(device)
+
+ models['instrumental'] = model
+ devices['instrumental'] = device
+ # -Vocal-
+ elif os.path.isfile(self.seperation_data['vocalModel']):
+ device = torch.device('cpu')
+ model = nets.CascadedASPPNet(self.seperation_data['n_fft'])
+ model.load_state_dict(torch.load(self.seperation_data['vocalModel'],
+ map_location=device))
+ if torch.cuda.is_available() and self.seperation_data['gpuConversion']:
+ device = torch.device('cuda:0')
+ model.to(device)
+
+ models['vocal'] = model
+ devices['vocal'] = device
+ # -Stack-
+ if os.path.isfile(self.seperation_data['stackModel']):
+ device = torch.device('cpu')
+ model = nets.CascadedASPPNet(self.seperation_data['n_fft'])
+ model.load_state_dict(torch.load(self.seperation_data['stackModel'],
+ map_location=device))
+ if torch.cuda.is_available() and self.seperation_data['gpuConversion']:
+ device = torch.device('cuda:0')
+ model.to(device)
+
+ models['stack'] = model
+ devices['stack'] = device
+
+ return models, devices
+
+ def get_total_loops() -> int:
+ """
+ Determine how many loops the program will
+ have to prepare for
+ """
+ if self.seperation_data['stackOnly']:
+ # Stack Conversion Only
+ total_loops = self.seperation_data['stackPasses']
+ else:
+ # 1 for the instrumental/vocal
+ total_loops = 1
+ # Add number of stack pass loops
+ total_loops += self.seperation_data['stackPasses']
+
+ return total_loops
+
+ # -Get data-
+ total_loops = get_total_loops()
+ folder_path, file_add_on = get_folderPath_fileAddOn()
+ self.logger.info('Loading models...')
+ models, devices = get_models_devices()
+
+ # -Set data-
+ self.general_data['total_files'] = len(self.seperation_data['input_paths'])
+ self.general_data['total_loops'] = total_loops
+ self.general_data['folder_path'] = folder_path
+ self.general_data['file_add_on'] = file_add_on
+ self.models = models
+ self.devices = devices
+
+ def _check_for_valid_inputs(self):
+ """
+ Check if all inputs have been entered correctly.
+
+ If errors are found, an exception is raised
+ """
+ # Check input paths
+ if not len(self.seperation_data['input_paths']):
+ # No music file specified
+ raise Exception('No music file to seperate defined!')
+ if (not isinstance(self.seperation_data['input_paths'], tuple) and
+ not isinstance(self.seperation_data['input_paths'], list)):
+ # Music file not specified in a list or tuple
+ raise Exception('Please specify your music file path/s in a list or tuple!')
+ for input_path in self.seperation_data['input_paths']:
+ # Go through each music file
+ if not os.path.isfile(input_path):
+ # Invalid path
+ raise Exception(f'Invalid music file! Please make sure that the file still exists or that the path is valid!\nPath: "{input_path}"') # nopep8
+ # Output path
+ if (not os.path.isdir(self.seperation_data['export_path']) and
+ not self.seperation_data['export_path'] == ''):
+ # Export path either invalid or not specified
+ raise Exception(f'Invalid export directory! Please make sure that the directory still exists or that the path is valid!\nPath: "{self.seperation_data["export_path"]}"') # nopep8
+
+ # Check models
+ if not self.seperation_data['useModel'] in ['vocal', 'instrumental']:
+ # Invalid 'useModel'
+ raise Exception("Parameter 'useModel' has to be either 'vocal' or 'instrumental'")
+ if not os.path.isfile(self.seperation_data[f"{self.seperation_data['useModel']}Model"]):
+ # No or invalid instrumental/vocal model given
+ # but model is needed
+ raise Exception(f"Not specified or invalid model path for {self.seperation_data['useModel']} model!")
+ if (self.seperation_data['stackOnly'] or
+ self.seperation_data['stackPasses'] > 0):
+ # First check if stacked model is needed
+ if not os.path.isfile(self.seperation_data['stackModel']):
+ # No or invalid stack model given
+ # but model is needed
+ raise Exception(f"Not specified or invalid model path for stacked model!")
+
+ def _seperate(self, file_path: str, file_num: int):
+ """
+ Seperate given music file,
+ file_num is used to determine progress
+ """
+
+ # -Update file specific variables-
+ self.loop_data['file_num'] = file_num
+ self.loop_data['music_file'] = file_path
+ self.loop_data['file_base_name'] = self._get_file_base_name(file_path)
+
+ for loop_num in range(self.general_data['total_loops']):
+ self.loop_data['loop_num'] = loop_num
+ # -Get loop specific variables-
+ command_base_text = self._get_base_text()
+ model_device = self._get_model_device_file()
+ constants = self._get_constants(model_device['model_name'])
+ # -Update loop specific variables
+ self.loop_data['constants'] = constants
+ self.loop_data['command_base_text'] = command_base_text
+ self.loop_data['model_device'] = model_device
+
+ # -Seperation-
+ if not self.loop_data['loop_num']:
+ # First loop
+ self._load_wave_source()
+ self._wave_to_spectogram()
+ if self.seperation_data['postProcess']:
+ # Postprocess
+ self._post_process()
+ self._inverse_stft_of_instrumentals_and_vocals()
+ self._save_files()
+ else:
+ # End of seperation
+ if self.seperation_data['outputImage']:
+ self._save_mask()
+
+ self.write_to_gui(text='Completed Seperation!\n',
+ progress_step=1)
+
+ # -Data Getter Methods-
+ def _get_base_text(self) -> str:
+ """
+ Determine the prefix text of the console
+ """
+ loop_add_on = ''
+ if self.general_data['total_loops'] > 1:
+ # More than one loop for conversion
+ loop_add_on = f" ({self.loop_data['loop_num']+1}/{self.general_data['total_loops']})"
+
+ return 'File {file_num}/{total_files}:{loop} '.format(file_num=self.loop_data['file_num'],
+ total_files=self.general_data['total_files'],
+ loop=loop_add_on)
+
+ def _get_constants(self, model_name: str) -> dict:
+ """
+ Get the sr, hop_length, window_size, n_fft
+ """
+ if self.loop_data['loop_num'] == 0:
+ # Instrumental/Vocal Model
+ seperation_params = {
+ 'sr': self.seperation_data['sr_stacked'],
+ 'hop_length': self.seperation_data['hop_length_stacked'],
+ 'window_size': self.seperation_data['window_size_stacked'],
+ 'n_fft': self.seperation_data['n_fft_stacked'],
+ }
+ else:
+ # Stacked model
+ seperation_params = {
+ 'sr': self.seperation_data['sr'],
+ 'hop_length': self.seperation_data['hop_length'],
+ 'window_size': self.seperation_data['window_size'],
+ 'n_fft': self.seperation_data['n_fft'],
+ }
+ if self.seperation_data['customParameters']:
+ # Typed constants are fixed
+ return seperation_params
+
+ # -Decode Model Name-
+ text = model_name.replace('.pth', '')
+ text_parts = text.split('_')[1:]
+ for text_part in text_parts:
+ if 'sr' in text_part:
+ text_part = text_part.replace('sr', '')
+ if text_part.isdecimal():
+ try:
+ seperation_params['sr'] = int(text_part)
+ continue
+ except ValueError:
+ # Cannot convert string to int
+ pass
+ if 'hl' in text_part:
+ text_part = text_part.replace('hl', '')
+ if text_part.isdecimal():
+ try:
+ seperation_params['hop_length'] = int(text_part)
+ continue
+ except ValueError:
+ # Cannot convert string to int
+ pass
+ if 'w' in text_part:
+ text_part = text_part.replace('w', '')
+ if text_part.isdecimal():
+ try:
+ seperation_params['window_size'] = int(text_part)
+ continue
+ except ValueError:
+ # Cannot convert string to int
+ pass
+ if 'nf' in text_part:
+ text_part = text_part.replace('nf', '')
+ if text_part.isdecimal():
+ try:
+ seperation_params['n_fft'] = int(text_part)
+ continue
+ except ValueError:
+ # Cannot convert string to int
+ pass
+
+ return seperation_params
+
+ def _get_model_device_file(self) -> dict:
+ """
+ Get the used models and devices for this loop
+ Also extract the model name and the music file
+ which will be used
+ """
+ model_device = {
+ 'model': None,
+ 'device': None,
+ 'model_name': None,
+ }
+ if self.seperation_data['stackOnly']:
+ # Stack Only Conversion
+ if os.path.isfile(self.seperation_data['stackModel']):
+ model_device['model'] = self.models['stack']
+ model_device['device'] = self.devices['stack']
+ model_device['model_name'] = os.path.basename(self.seperation_data['stackModel'])
+ else:
+ raise ValueError(f'Selected stack only model, however, stack model path file cannot be found\nPath: "{self.seperation_data["stackModel"]}"') # nopep8
+ elif not self.loop_data['loop_num']:
+ # First Loop
+ model_device['model'] = self.models[self.seperation_data['useModel']]
+ model_device['device'] = self.devices[self.seperation_data['useModel']]
+ model_device['model_name'] = os.path.basename(
+ self.seperation_data[f'{self.seperation_data["useModel"]}Model'])
+ else:
+ # Every other iteration
+ model_device['model'] = self.models['stack']
+ model_device['device'] = self.devices['stack']
+ model_device['model_name'] = os.path.basename(self.seperation_data['stackModel'])
+
+ return model_device
+
+ def _get_file_base_name(self, file_path: str) -> str:
+ """
+ Get the path infos for the given music file
+ """
+ return f"{self.loop_data['file_num']}_{os.path.splitext(os.path.basename(file_path))[0]}"
+
+ # -Seperation Methods-
+ def _load_wave_source(self):
+ """
+ Load the wave source
+ """
+ self.write_to_gui(text='Loading wave source...',
+ progress_step=0)
+ try:
+ X, sampling_rate = librosa.load(path=self.loop_data['music_file'],
+ sr=self.loop_data['constants']['sr'],
+ mono=False, dtype=np.float32,
+ res_type=self.seperation_data['resType'])
+ except audioread.NoBackendError:
+ raise Exception(
+ f'Invalid music file provided! Please check its validity.\nFile: "{self.loop_data["music_file"]}"')
+
+ if X.ndim == 1:
+ X = np.asarray([X, X])
+
+ self.loop_data['X'] = X
+ self.loop_data['sampling_rate'] = sampling_rate
+
+ def _wave_to_spectogram(self):
+ """
+ Wave to spectogram
+ """
+ def preprocess(X_spec):
+ X_mag = np.abs(X_spec)
+ X_phase = np.angle(X_spec)
+
+ return X_mag, X_phase
+
+ def execute(X_mag_pad, roi_size, n_window, device, model, progrs_info: str = ''):
+ model.eval()
+ with torch.no_grad():
+ preds = []
+ bar_format = '{desc} |{bar}{r_bar}'
+ pbar = tqdm(range(n_window), bar_format=bar_format)
+
+ for progrs, i in enumerate(pbar):
+ # Progress management
+ if progrs_info == '1/2':
+ progres_step = 0.1 + 0.35 * (progrs / n_window)
+ elif progrs_info == '2/2':
+ progres_step = 0.45 + 0.35 * (progrs / n_window)
+ else:
+ progres_step = 0.1 + 0.7 * (progrs / n_window)
+ self.write_to_gui(progress_step=progres_step)
+
+ progress = self._get_progress(progres_step)
+ text = f'{int(progress)} %'
+ if progress < 10:
+ text += ' '
+ pbar.set_description_str(text)
+
+ start = i * roi_size
+ X_mag_window = X_mag_pad[None, :, :,
+ start:start + self.seperation_data['window_size']]
+ X_mag_window = torch.from_numpy(X_mag_window).to(device)
+
+ pred = model.predict(X_mag_window)
+
+ pred = pred.detach().cpu().numpy()
+ preds.append(pred[0])
+
+ pred = np.concatenate(preds, axis=2)
+
+ return pred
+
+ def inference(X_spec, device, model):
+ 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,
+ self.seperation_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)
+ pred = pred[:, :, :n_frame]
+
+ return pred * coef, X_mag, np.exp(1.j * X_phase)
+
+ def inference_tta(X_spec, device, model):
+ 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,
+ self.seperation_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, progrs_info='1/2')
+ pred = pred[:, :, :n_frame]
+
+ 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, progrs_info='2/2')
+ 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)
+
+ self.write_to_gui(text='Stft of wave source...',
+ progress_step=0.1)
+
+ if not self.loop_data['loop_num']:
+ X = spec_utils.wave_to_spectrogram(wave=self.loop_data['X'],
+ hop_length=self.seperation_data['hop_length'],
+ n_fft=self.seperation_data['n_fft'])
+ else:
+ X = self.loop_data['temp_spectogramm']
+
+ if self.seperation_data['tta']:
+ prediction, X_mag, X_phase = inference_tta(X_spec=X,
+ device=self.loop_data['model_device']['device'],
+ model=self.loop_data['model_device']['model'])
+ else:
+ prediction, X_mag, X_phase = inference(X_spec=X,
+ device=self.loop_data['model_device']['device'],
+ model=self.loop_data['model_device']['model'])
+
+ self.loop_data['prediction'] = prediction
+ self.loop_data['X'] = X
+ self.loop_data['X_mag'] = X_mag
+ self.loop_data['X_phase'] = X_phase
+
+ def _post_process(self):
+ """
+ Post process
+ """
+ self.write_to_gui(text='Post processing...',
+ progress_step=0.8)
+
+ pred_inv = np.clip(self.loop_data['X_mag'] - self.loop_data['prediction'], 0, np.inf)
+ prediction = spec_utils.mask_silence(self.loop_data['prediction'], pred_inv)
+
+ self.loop_data['prediction'] = prediction
+
+ def _inverse_stft_of_instrumentals_and_vocals(self):
+ """
+ Inverse stft of instrumentals and vocals
+ """
+ self.write_to_gui(text='Inverse stft of instruments and vocals...',
+ progress_step=0.85)
+
+ y_spec = self.loop_data['prediction'] * self.loop_data['X_phase']
+ v_spec = np.clip(self.loop_data['X_mag'] - self.loop_data['prediction'],
+ 0, np.inf) * self.loop_data['X_phase']
+
+ if self.loop_data['loop_num'] == (self.general_data['total_loops'] - 1):
+ # Only compute wave on last loop
+ wav_instrument = spec_utils.spectrogram_to_wave(y_spec,
+ hop_length=self.seperation_data['hop_length'])
+ self.loop_data['wav_instrument'] = wav_instrument
+ wav_vocals = spec_utils.spectrogram_to_wave(v_spec,
+ hop_length=self.seperation_data['hop_length'])
+ self.loop_data['wav_vocals'] = wav_vocals
+
+ # Needed for mask creation
+ self.loop_data['y_spec'] = y_spec
+ self.loop_data['v_spec'] = v_spec
+
+ self.loop_data['temp_spectogramm'] = y_spec
+
+ def _save_files(self):
+ """
+ Save the files
+ """
+ def get_vocal_instrumental_name() -> Tuple[str, str, str]:
+ """
+ Get vocal and instrumental file names and update the
+ folder_path temporarily if needed
+ """
+ loop_num = self.loop_data['loop_num']
+ total_loops = self.general_data['total_loops']
+ file_base_name = self.loop_data['file_base_name']
+ vocal_name = None
+ instrumental_name = None
+ folder_path = self.general_data['folder_path']
+
+ # Get the Suffix Name
+ if (not loop_num or
+ loop_num == (total_loops - 1)): # First or Last Loop
+ if self.seperation_data['stackOnly']:
+ if loop_num == (total_loops - 1): # Last Loop
+ if not (total_loops - 1): # Only 1 Loop
+ vocal_name = '(Vocals)'
+ instrumental_name = '(Instrumental)'
+ else:
+ vocal_name = '(Vocal_Final_Stacked_Output)'
+ instrumental_name = '(Instrumental_Final_Stacked_Output)'
+ elif self.seperation_data['useModel'] == 'instrumental':
+ if not loop_num: # First Loop
+ vocal_name = '(Vocals)'
+ if loop_num == (total_loops - 1): # Last Loop
+ if not (total_loops - 1): # Only 1 Loop
+ instrumental_name = '(Instrumental)'
+ else:
+ instrumental_name = '(Instrumental_Final_Stacked_Output)'
+ elif self.seperation_data['useModel'] == 'vocal':
+ if not loop_num: # First Loop
+ instrumental_name = '(Instrumental)'
+ if loop_num == (total_loops - 1): # Last Loop
+ if not (total_loops - 1): # Only 1 Loop
+ vocal_name = '(Vocals)'
+ else:
+ vocal_name = '(Vocals_Final_Stacked_Output)'
+ if self.seperation_data['useModel'] == 'vocal':
+ # Reverse names
+ vocal_name, instrumental_name = instrumental_name, vocal_name
+ elif self.seperation_data['saveAllStacked']:
+ stacked_folder_name = file_base_name + ' Stacked Outputs' # nopep8
+ folder_path = os.path.join(folder_path, stacked_folder_name)
+
+ if not os.path.isdir(folder_path):
+ os.mkdir(folder_path)
+
+ if self.seperation_data['stackOnly']:
+ vocal_name = f'(Vocal_{loop_num}_Stacked_Output)'
+ instrumental_name = f'(Instrumental_{loop_num}_Stacked_Output)'
+ elif (self.seperation_data['useModel'] == 'vocal' or
+ self.seperation_data['useModel'] == 'instrumental'):
+ vocal_name = f'(Vocals_{loop_num}_Stacked_Output)'
+ instrumental_name = f'(Instrumental_{loop_num}_Stacked_Output)'
+
+ if self.seperation_data['useModel'] == 'vocal':
+ # Reverse names
+ vocal_name, instrumental_name = instrumental_name, vocal_name
+ return vocal_name, instrumental_name, folder_path
+
+ self.write_to_gui(text='Saving Files...',
+ progress_step=0.9)
+
+ vocal_name, instrumental_name, folder_path = get_vocal_instrumental_name()
+
+ # -Save files-
+ instrumental_file_name = f"{self.loop_data['file_base_name']}_{instrumental_name}{self.general_data['file_add_on']}.wav"
+ vocal_file_name = f"{self.loop_data['file_base_name']}_{vocal_name}{self.general_data['file_add_on']}.wav"
+ self.latest_instrumental_path = os.path.join(folder_path,
+ instrumental_file_name)
+ self.latest_vocal_path = os.path.join(folder_path,
+ vocal_file_name)
+ # Instrumental
+ if (instrumental_name is not None and
+ self.seperation_data['save_instrumentals']):
+
+ sf.write(self.latest_instrumental_path,
+ self.loop_data['wav_instrument'].T, self.loop_data['sampling_rate'])
+ # Vocal
+ if (vocal_name is not None and
+ self.seperation_data['save_vocals']):
+ sf.write(self.latest_vocal_path,
+ self.loop_data['wav_vocals'].T, self.loop_data['sampling_rate'])
+
+ def _save_mask(self):
+ """
+ Save output image
+ """
+ mask_path = os.path.join(self.general_data['folder_path'], self.loop_data['file_base_name'])
+ with open('{}_Instruments.jpg'.format(mask_path), mode='wb') as f:
+ image = spec_utils.spectrogram_to_image(self.loop_data['y_spec'])
+ _, bin_image = cv2.imencode('.jpg', image)
+ bin_image.tofile(f)
+ with open('{}_Vocals.jpg'.format(mask_path), mode='wb') as f:
+ image = spec_utils.spectrogram_to_image(self.loop_data['v_spec'])
+ _, bin_image = cv2.imencode('.jpg', image)
+ bin_image.tofile(f)
+
+ # -Other Methods-
+ def _get_progress(self, progress_step: Optional[float] = None) -> float:
+ """
+ Get current conversion progress in percent
+ """
+ if progress_step is not None:
+ self.loop_data['progress_step'] = progress_step
+ try:
+ base = (100 / self.general_data['total_files'])
+ progress = base * (self.loop_data['file_num'] - 1)
+ progress += (base / self.general_data['total_loops']) * \
+ (self.loop_data['loop_num'] + self.loop_data['progress_step'])
+ except TypeError:
+ # One data point not specified yet
+ progress = 0
+
+ return progress
+
+
+class WorkerSignals(QtCore.QObject):
+ '''
+ Defines the signals available from a running worker thread.
+
+ Supported signals are:
+
+ finished
+ str: Time elapsed
+ message
+ str: Message to write to GUI
+ progress
+ int (0-100): Progress update
+ error
+ Tuple[str, str]:
+ Index 0: Error Message
+ Index 1: Detailed Message
+
+ '''
+ start = QtCore.Signal()
+ finished = QtCore.Signal(str, tuple)
+ message = QtCore.Signal(str)
+ progress = QtCore.Signal(int)
+ error = QtCore.Signal(tuple)
+
+
+class VocalRemoverWorker(VocalRemover, QtCore.QRunnable):
+ '''
+ Threaded Vocal Remover
+
+ Only use in conjunction with GUI
+ '''
+
+ def __init__(self, logger, seperation_data: dict = {}):
+ super(VocalRemoverWorker, self).__init__(seperation_data, logger=logger)
+ super(VocalRemover, self).__init__(seperation_data, logger=logger)
+ super(QtCore.QRunnable, self).__init__()
+ self.signals = WorkerSignals()
+ self.logger = logger
+ self.seperation_data = seperation_data
+ self.setAutoDelete(False)
+
+ @ QtCore.Slot()
+ def run(self):
+ """
+ Seperate files
+ """
+ stime = time.perf_counter()
+
+ try:
+ self.signals.start.emit()
+ self.logger.info(msg='----- The seperation has started! -----')
+ try:
+ self.seperate_files()
+ except RuntimeError:
+ # Application was forcefully closed
+ print('Application forcefully closed')
+ return
+ except Exception as e:
+ self.logger.exception(msg='An Exception has occurred!')
+ traceback_text = ''.join(traceback.format_tb(e.__traceback__))
+ message = f'Traceback Error: "{traceback_text}"\n{type(e).__name__}: "{e}"\nIf the issue is not clear, please contact the creator and attach a screenshot of the detailed message with the file and settings that caused it!'
+ print(traceback_text)
+ print(type(e).__name__, e)
+ self.signals.error.emit([str(e), message])
+ return
+ elapsed_seconds = int(time.perf_counter() - stime)
+ elapsed_time = str(dt.timedelta(seconds=elapsed_seconds))
+ self.signals.finished.emit(elapsed_time, [self.latest_instrumental_path, self.latest_vocal_path])
+
+ def write_to_gui(self, text: Optional[str] = None, include_base_text: bool = True, progress_step: Optional[float] = None):
+ if text is not None:
+ if include_base_text:
+ # Include base text
+ text = f"{self.loop_data['command_base_text']} {text}"
+ self.signals.message.emit(text)
+
+ if progress_step is not None:
+ self.signals.progress.emit(self._get_progress(progress_step))
+
+ def _save_files(self):
+ """
+ Also save files in temp location for in GUI audio playback
+ """
+ super()._save_files()
+ if self.loop_data['loop_num'] == (self.general_data['total_loops'] - 1): # Last loop
+ sf.write(os.path.join(ResourcePaths.tempDir, self.latest_instrumental_path),
+ self.loop_data['wav_instrument'].T, self.loop_data['sampling_rate'])
+ sf.write(os.path.join(ResourcePaths.tempDir, self.latest_vocal_path),
+ self.loop_data['wav_vocals'].T, self.loop_data['sampling_rate'])
diff --git a/src/resources/themes/dark.qss b/src/resources/themes/dark.qss
index 1f9fbb2..dcbf2d4 100644
--- a/src/resources/themes/dark.qss
+++ b/src/resources/themes/dark.qss
@@ -2,6 +2,9 @@
* {
font: 10pt "Yu Gothic UI";
}
+*::disabled {
+ color: #888;
+}
*[title="true"],
QGroupBox {
font: 15pt "Yu Gothic UI";
@@ -50,11 +53,11 @@ QRadioButton[menu="true"]::indicator::hover {
}
QRadioButton[menu="true"]::checked,
QRadioButton[menu="true"]::indicator::checked {
- border-left: 5px solid qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0.505682 rgba(0, 120, 212, 255), stop:1 rgba(255, 255, 255, 0));
+ border-left: 5px solid qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0.505682 #368ADD, stop:1 rgba(255, 255, 255, 0));
}
/* Command clear */
QPushButton[clear="true"] {
- border: 2px solid rgb(109, 213, 237);
+ border: 2px solid #368ADD;
border-radius: 5px;
color: #FFF;
}
@@ -71,7 +74,7 @@ QPushButton[language="true"] {
border: none;
}
QPushButton[language="true"]:checked {
- border: 3px solid rgb(109, 213, 237);
+ border: 3px solid #368ADD;
}
/* Export */
QLabel[path="true"] {
@@ -83,8 +86,8 @@ QLabel[path="true"] {
QPushButton[seperate="true"] {
border-width: 2px;
border-style: solid;
- border-radius: 15px;
- border-color: rgb(109, 213, 237);
+ border-radius: 7px;
+ border-color: #368ADD;
background-color: rgba(109, 213, 237, 4);
}
QPushButton[seperate="true"]:hover {
@@ -98,33 +101,37 @@ QPushButton[musicSelect="true"] {
color: rgb(160, 160, 160);
border-width: 3px;
border-style: dotted;
- border-color: rgb(160, 160, 160);
+ background-color: #2d2d2d;
+ border-color: #424242;
border-radius: 5px;
}
-/* QPushButton[musicSelect="true"]:hover {
- background-color: rgb(2, 24, 53);
+QPushButton[musicSelect="true"]:hover {
+ background-color: #333333;
}
QPushButton[musicSelect="true"]:pressed {
- background-color: rgb(1, 24, 61);
-} */
+ background-color: #404040;
+}
+
QListWidget[musicSelect="true"] {
font-size: 13pt;
- background-color: rgb(12, 23, 40);
- alternate-background-color: rgb(2, 18, 40);
+ background-color: #303030;
+ alternate-background-color: #424242;
outline: none;
+ border-radius: 5px;
}
QListWidget[musicSelect="true"]::item {
outline: none;
border: none;
+ border-radius: 5px;
}
-QScrollBar[musicSelect="true"] {
- background-color: none;
+QListWidget[musicSelect="true"]::item:selected {
+ background-color: #368ADD;
}
/* Command Line*/
QTextBrowser {
border-left: 2px;
border-style: solid;
- border-color: rgb(109, 213, 237);
+ border-color: #368ADD;
font: 8pt "Courier";
}
/* Audio Player */
@@ -132,7 +139,7 @@ QLabel[audioPlayer="true"] {
color: rgba(160, 160, 160, 80);
border-width: 3px;
border-style: dotted;
- border-color: rgb(60, 60, 80);
+ border-color: #424242;
border-radius: 5px;
}
QPushButton[audioPlayer="true"] {
@@ -145,8 +152,8 @@ QSlider[audioPlayer="true"]::groove:horizontal {
}
QSlider[audioPlayer="true"]::handle:horizontal {
- background-color: rgb(109, 213, 237);
- border: 2px solid rgb(109, 213, 237);
+ background-color: #368ADD;
+ border: 2px solid #368ADD;
width: 10px;
margin-top: -5px;
margin-bottom: -5px;
diff --git a/src/resources/themes/light.qss b/src/resources/themes/light.qss
index 94a42fb..e5ca387 100644
--- a/src/resources/themes/light.qss
+++ b/src/resources/themes/light.qss
@@ -2,6 +2,9 @@
* {
font: 10pt "Yu Gothic UI";
}
+*::disabled {
+ color: #888;
+}
*[title="true"],
QGroupBox {
font: 15pt "Yu Gothic UI";
@@ -94,6 +97,7 @@ QPushButton[seperate="true"]:pressed {
background-color: rgba(109, 213, 237, 30);
}
/* Music File Selection */
+/*
QPushButton[musicSelect="true"] {
color: rgb(160, 160, 160);
border-width: 3px;
@@ -119,7 +123,7 @@ QListWidget[musicSelect="true"]::item {
}
QScrollBar[musicSelect="true"] {
background-color: none;
-}
+}*/
/* Command Line*/
QTextBrowser {
border-left: 2px;
@@ -132,7 +136,7 @@ QLabel[audioPlayer="true"] {
color: rgba(160, 160, 160, 80);
border-width: 3px;
border-style: dotted;
- border-color: rgb(60, 60, 80);
+ border-color: #424242;
border-radius: 5px;
}
QPushButton[audioPlayer="true"] {
diff --git a/src/resources/themes/temp.qss b/src/resources/themes/temp.qss
deleted file mode 100644
index fc7caf1..0000000
--- a/src/resources/themes/temp.qss
+++ /dev/null
@@ -1,180 +0,0 @@
-/* --- General --- */
-* {
- font: 10pt "Yu Gothic UI";
- color: rgb(255, 255, 255);
- background-color: none;
- background: rgb(0, 0, 0);
-}
-*[title="true"],
-QGroupBox {
- font: 15pt "Yu Gothic UI";
-}
-QLineEdit,
-QComboBox {
- background: none;
- color: #000;
-}
-QCheckBox {
- color: #CCC;
-}
-QToolTip {
- color: rgb(0, 0, 0);
-}
-QScrollBar:horizontal {
- border: 2px solid green;
- background: #171717;
- height: 15px;
- margin: 0px 40px 0 0px;
-}
-QScrollBar::handle:vertical {
- background: #4d4d4d;
- min-hieght: 20px;
-}
-
-QComboBox QAbstractItemView {
- border: 1px solid rgba(0, 120, 212, 122);
- outline: none;
- background-color: rgb(31, 31, 31);
- selection-background-color: rgb(51, 51, 51);
-}
-QPushButton {
- background-color: rgb(51, 121, 217);
- border: none;
-}
-QPushButton:hover {
- background-color: rgb(173, 216, 255);
-}
-QPushButton:pressed {
- background-color: rgb(23, 66, 118);
-}
-QLineEdit:disabled {
- color: #222;
- border: 1px solid gray;
- background-color: #999;
-}
-/* --- Settings Window Specific --- */
-/* Left Menu */
-QRadioButton[menu="true"]::indicator {
- width: 0px;
- height: 0px;
-}
-QFrame[menu="true"] {
- background-color: rgb(31, 31, 31);
-}
-QRadioButton[menu="true"]::unchecked,
-QRadioButton[menu="true"]::indicator::unchecked {
- background-color: rgb(31, 31, 31);
- padding: 1px;
-}
-QRadioButton[menu="true"]::unchecked::hover,
-QRadioButton[menu="true"]::indicator::hover {
- background-color: rgb(51, 51, 51);
-}
-QRadioButton[menu="true"]::checked,
-QRadioButton[menu="true"]::indicator::checked {
- border-left: 5px solid qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0.505682 rgba(0, 120, 212, 255), stop:1 rgba(255, 255, 255, 0));
-}
-/* Command clear */
-QPushButton[clear="true"] {
- border: 2px solid rgb(109, 213, 237);
- border-radius: 5px;
- color: #FFF;
-}
-QPushButton[clear="true"]:hover {
- background-color: rgb(25, 45, 60);
-}
-QPushButton[clear="true"]:pressed {
- background-color: rgb(49, 96, 107);
-}
-/* Language */
-QPushButton[language="true"] {
- border-radius: 10px;
- background-color: rgba(255, 255, 255, 5);
- border: none;
-}
-QPushButton[language="true"]:checked {
- border: 3px solid rgb(109, 213, 237);
-}
-/* Export */
-QLabel[path="true"] {
- font: 7pt "Yu Gothic UI";
- color: #ccc;
-}
-/* --- Main Window Specific --- */
-/* Seperate Button */
-QPushButton[seperate="true"] {
- border-width: 2px;
- border-style: solid;
- border-radius: 15px;
- border-color: rgb(109, 213, 237);
- background-color: rgba(109, 213, 237, 4);
-}
-QPushButton[seperate="true"]:hover {
- background-color: rgba(109, 213, 237, 10);
-}
-QPushButton[seperate="true"]:pressed {
- background-color: rgba(109, 213, 237, 30);
-}
-/* Music File Selection */
-QPushButton[musicSelect="true"] {
- color: rgb(160, 160, 160);
- border-width: 3px;
- border-style: dotted;
- border-color: rgb(160, 160, 160);
- border-radius: 5px;
-}
-QPushButton[musicSelect="true"]:hover {
- background-color: rgb(2, 24, 53);
-}
-QPushButton[musicSelect="true"]:pressed {
- background-color: rgb(1, 24, 61);
-}
-QListWidget[musicSelect="true"] {
- font-size: 13pt;
- background-color: rgb(12, 23, 40);
- alternate-background-color: rgb(2, 18, 40);
- outline: none;
-}
-QListWidget[musicSelect="true"]::item {
- outline: none;
- border: none;
-}
-QScrollBar[musicSelect="true"] {
- background-color: none;
-}
-/* Command Line*/
-QTextBrowser {
- border-left: 2px;
- border-style: solid;
- border-color: rgb(109, 213, 237);
- font: 8pt "Courier";
-}
-/* Audio Player */
-QLabel[audioPlayer="true"] {
- color: rgba(160, 160, 160, 80);
- border-width: 3px;
- border-style: dotted;
- border-color: rgb(60, 60, 80);
- border-radius: 5px;
-}
-QPushButton[audioPlayer="true"] {
- border: none;
-}
-QSlider[audioPlayer="true"]::groove:horizontal {
- background-color: rgb(44, 51, 65);
- height: 4px;
- border-radius: 2px;
-}
-
-QSlider[audioPlayer="true"]::handle:horizontal {
- background-color: rgb(109, 213, 237);
- border: 2px solid rgb(109, 213, 237);
- width: 10px;
- margin-top: -5px;
- margin-bottom: -5px;
- border-radius: 5px;
-}
-
-QSlider[audioPlayer="true"]::handle:horizontal:hover {
- border-radius: 5px;
-}
diff --git a/src/windows/design/mainwindow_ui.py b/src/windows/design/mainwindow_ui.py
index f6addfb..a769c5d 100644
--- a/src/windows/design/mainwindow_ui.py
+++ b/src/windows/design/mainwindow_ui.py
@@ -17,13 +17,13 @@ class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
- MainWindow.resize(911, 559)
+ MainWindow.resize(947, 559)
MainWindow.setMinimumSize(QSize(0, 0))
MainWindow.setStyleSheet(u"")
self.horizontalLayout_2 = QHBoxLayout(MainWindow)
self.horizontalLayout_2.setSpacing(0)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
- self.horizontalLayout_2.setContentsMargins(0, 0, 5, 0)
+ self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.frame_5 = QFrame(MainWindow)
self.frame_5.setObjectName(u"frame_5")
self.frame_5.setMinimumSize(QSize(650, 0))
@@ -87,10 +87,9 @@ class Ui_MainWindow(object):
self.stackedWidget_musicFiles.addWidget(self.page_select)
self.page_display = QWidget()
self.page_display.setObjectName(u"page_display")
- self.verticalLayout_4 = QVBoxLayout(self.page_display)
- self.verticalLayout_4.setSpacing(0)
- self.verticalLayout_4.setObjectName(u"verticalLayout_4")
- self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_5 = QHBoxLayout(self.page_display)
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.horizontalLayout_5.setContentsMargins(0, 0, 0, 0)
self.listWidget_musicFiles = QListWidget(self.page_display)
self.listWidget_musicFiles.setObjectName(u"listWidget_musicFiles")
sizePolicy3 = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
@@ -99,7 +98,6 @@ class Ui_MainWindow(object):
sizePolicy3.setHeightForWidth(
self.listWidget_musicFiles.sizePolicy().hasHeightForWidth())
self.listWidget_musicFiles.setSizePolicy(sizePolicy3)
- self.listWidget_musicFiles.setMaximumSize(QSize(16777215, 16777215))
self.listWidget_musicFiles.setFrameShape(QFrame.NoFrame)
self.listWidget_musicFiles.setLineWidth(0)
self.listWidget_musicFiles.setHorizontalScrollBarPolicy(
@@ -108,11 +106,39 @@ class Ui_MainWindow(object):
QAbstractItemView.NoEditTriggers)
self.listWidget_musicFiles.setAlternatingRowColors(True)
self.listWidget_musicFiles.setSelectionMode(
- QAbstractItemView.NoSelection)
+ QAbstractItemView.ExtendedSelection)
self.listWidget_musicFiles.setWordWrap(True)
self.listWidget_musicFiles.setProperty("musicSelect", True)
- self.verticalLayout_4.addWidget(self.listWidget_musicFiles)
+ self.horizontalLayout_5.addWidget(self.listWidget_musicFiles)
+
+ self.frame_7 = QFrame(self.page_display)
+ self.frame_7.setObjectName(u"frame_7")
+ self.frame_7.setMaximumSize(QSize(35, 16777215))
+ self.frame_7.setFrameShape(QFrame.NoFrame)
+ self.frame_7.setFrameShadow(QFrame.Raised)
+ self.verticalLayout_4 = QVBoxLayout(self.frame_7)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
+ self.pushButton_add = QPushButton(self.frame_7)
+ self.pushButton_add.setObjectName(u"pushButton_add")
+ self.pushButton_add.setMinimumSize(QSize(35, 35))
+ self.pushButton_add.setMaximumSize(QSize(35, 35))
+ self.pushButton_add.setCursor(QCursor(Qt.PointingHandCursor))
+ self.pushButton_add.setText(u"+")
+
+ self.verticalLayout_4.addWidget(self.pushButton_add)
+
+ self.pushButton_delete = QPushButton(self.frame_7)
+ self.pushButton_delete.setObjectName(u"pushButton_delete")
+ self.pushButton_delete.setMinimumSize(QSize(35, 35))
+ self.pushButton_delete.setMaximumSize(QSize(35, 35))
+ self.pushButton_delete.setCursor(QCursor(Qt.PointingHandCursor))
+ self.pushButton_delete.setText(u"-")
+
+ self.verticalLayout_4.addWidget(self.pushButton_delete)
+
+ self.horizontalLayout_5.addWidget(self.frame_7, 0, Qt.AlignTop)
self.stackedWidget_musicFiles.addWidget(self.page_display)
@@ -306,6 +332,7 @@ class Ui_MainWindow(object):
self.pushButton_seperate.setObjectName(u"pushButton_seperate")
self.pushButton_seperate.setMinimumSize(QSize(160, 0))
self.pushButton_seperate.setMaximumSize(QSize(16777215, 50))
+ self.pushButton_seperate.setCursor(QCursor(Qt.PointingHandCursor))
self.pushButton_seperate.setStyleSheet(u"border-top-right-radius: 0px;\n"
"border-bottom-right-radius: 0px;")
self.pushButton_seperate.setProperty("seperate", True)
@@ -317,6 +344,7 @@ class Ui_MainWindow(object):
self.pushButton_settings.setObjectName(u"pushButton_settings")
self.pushButton_settings.setMinimumSize(QSize(50, 50))
self.pushButton_settings.setMaximumSize(QSize(50, 50))
+ self.pushButton_settings.setCursor(QCursor(Qt.PointingHandCursor))
self.pushButton_settings.setStyleSheet(u"border-left: none;\n"
"border-top-left-radius: 0px;\n"
"border-bottom-left-radius: 0px;")
@@ -393,11 +421,10 @@ class Ui_MainWindow(object):
self.horizontalLayout_2.addWidget(self.textBrowser_command)
self.horizontalLayout_2.setStretch(0, 3)
- self.horizontalLayout_2.setStretch(1, 2)
self.retranslateUi(MainWindow)
- self.stackedWidget_musicFiles.setCurrentIndex(0)
+ self.stackedWidget_musicFiles.setCurrentIndex(1)
self.stackedWidget_vocals.setCurrentIndex(0)
self.stackedWidget_instrumentals.setCurrentIndex(0)
diff --git a/src/windows/mainwindow.py b/src/windows/mainwindow.py
index 1334a6a..64a61f9 100644
--- a/src/windows/mainwindow.py
+++ b/src/windows/mainwindow.py
@@ -74,173 +74,6 @@ class MainWindow(QtWidgets.QWidget):
self.instrumentals_audioPlayer: AudioPlayer
self.vocals_audioPlayer: AudioPlayer
self.tempAudioFilePaths: Optional[Tuple[str, str]] = None
- # -Initialization methods-
-
- def setup_window(self):
- """
- Set up the window with binds, images, saved settings
-
- (Only run right after window initialization of main and settings window)
- """
- def load_geometry():
- """
- Load the geometry of this window
- """
- # Window is centered on primary window
- default_size = self.size()
- default_pos = QtCore.QPoint()
- default_pos.setX((self.app.primaryScreen().size().width() / 2) - default_size.width() / 2)
- default_pos.setY((self.app.primaryScreen().size().height() / 2) - default_size.height() / 2)
- # Get data
- self.settings.beginGroup(self.__class__.__name__.lower())
- size = self.settings.value('size',
- default_size)
- pos = self.settings.value('pos',
- default_pos)
- isMaximized = self.settings.value('isMaximized',
- False,
- type=bool)
- self.settings.endGroup()
- # Apply data
- self.move(pos)
- if isMaximized:
- self.setWindowState(Qt.WindowMaximized)
- else:
- self.resize(size)
-
- def load_images():
- """
- Load the images for this window and assign them to their widgets
- """
- # Settings button
- self.settings_img = QtGui.QPixmap(ResourcePaths.images.settings)
- self.ui.pushButton_settings.setIcon(self.settings_img)
- self.ui.pushButton_settings.setIconSize(QtCore.QSize(25, 25))
-
- def bind_widgets():
- """
- Bind the widgets here
- """
- # -Override binds-
- # Music file drag & drop
- self.ui.stackedWidget_musicFiles.dragEnterEvent = self.stackedWidget_musicFiles_dragEnterEvent
- self.ui.stackedWidget_musicFiles.dropEvent = self.stackedWidget_musicFiles_dropEvent
- self.ui.pushButton_musicFiles.clicked.connect(self.pushButton_musicFiles_clicked)
- # -Pushbuttons-
- self.ui.pushButton_settings.clicked.connect(self.pushButton_settings_clicked)
- self.ui.pushButton_seperate.clicked.connect(self.pushButton_seperate_clicked)
-
- def create_animation_objects():
- """
- Create the animation objects that are used
- multiple times here
- """
- def style_progressbar():
- """
- Style pogressbar manually as when styled in Qt Designer
- a bug occurs that prevents smooth animation of progressbar
- """
- self.ui.progressBar.setStyleSheet("""QProgressBar:horizontal {
- border: 0px solid gray;
- }
- QProgressBar::chunk {
- background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0.0795455 rgba(33, 147, 176, 255), stop:1 rgba(109, 213, 237, 255));
- border-top-right-radius: 2px;
- border-bottom-right-radius: 2px;
- } """)
- self.seperation_update_progress(0)
- # -Progress Bar-
- self.pbar_animation = QtCore.QPropertyAnimation(self.ui.progressBar, b"value",
- parent=self)
- # This is all to prevent the progressbar animation not working propertly
- self.pbar_animation.setDuration(8)
- self.pbar_animation.setStartValue(0)
- self.pbar_animation.setEndValue(8)
- self.pbar_animation.start()
- self.pbar_animation.setDuration(500)
- QtCore.QTimer.singleShot(1000, lambda: style_progressbar())
- # -Settings Icon-
-
- def rotate_settings_icon():
- rotation = self.settings_ani.currentValue()
- t = QtGui.QTransform()
- t = t.rotate(rotation)
- new_pixmap = self.settings_img.transformed(t, QtCore.Qt.FastTransformation)
- xoffset = (new_pixmap.width() - self.settings_img.width()) / 2
- yoffset = (new_pixmap.height() - self.settings_img.height()) / 2
- new_pixmap = new_pixmap.copy(xoffset, yoffset, self.settings_img.width(), self.settings_img.height())
- self.ui.pushButton_settings.setIcon(new_pixmap)
-
- self.settings_ani = QtCore.QVariantAnimation(self)
- self.settings_ani.setDuration(1750)
- self.settings_ani.setEasingCurve(QtCore.QEasingCurve.OutBack)
- self.settings_ani.setStartValue(0.0)
- self.settings_ani.setEndValue(-180.0)
- self.settings_ani.valueChanged.connect(rotate_settings_icon)
-
- # -Before setup-
- self.logger.info('Main -> Setting up',
- indent_forwards=True)
- # Load saved settings for widgets
- self._load_data()
- # Audio Players
- self.instrumentals_audioPlayer = AudioPlayer(self.app,
- self.ui.pushButton_play_instrumentals,
- self.ui.horizontalSlider_instrumentals,
- self.ui.pushButton_menu_instrumentals)
- self.vocals_audioPlayer = AudioPlayer(self.app,
- self.ui.pushButton_play_vocals,
- self.ui.horizontalSlider_vocals,
- self.ui.pushButton_menu_vocals)
- # Temp func
- self.tempAudioFilePaths = [os.path.join(ResourcePaths.tempDir, 'temp_instrumentals.wav'),
- os.path.join(ResourcePaths.tempDir, 'temp_vocals.wav')]
- self._deactivate_audio_players()
-
- # -Setup-
- load_geometry()
- load_images()
- bind_widgets()
- create_animation_objects()
- self.show()
-
- # -After setup-
- # Create WinTaskbar
- self.winTaskbar = QWinTaskbarButton(self)
- self.winTaskbar.setWindow(self.windowHandle())
- self.winTaskbar_progress = self.winTaskbar.progress()
- # Create instance
- self.vocalRemoverRunnable = converter_v4.VocalRemoverWorker(logger=self.logger)
- # Bind events
- self.vocalRemoverRunnable.signals.start.connect(self.seperation_start)
- self.vocalRemoverRunnable.signals.message.connect(self.seperation_write)
- self.vocalRemoverRunnable.signals.progress.connect(self.seperation_update_progress)
- self.vocalRemoverRunnable.signals.error.connect(self.seperation_error)
- self.vocalRemoverRunnable.signals.finished.connect(self.seperation_finish)
- # Late update
- self.update_window()
- self.logger.indent_backwards()
-
- def _load_data(self, default: bool = False):
- """
- Load the data for this window
-
- (Only run right after window initialization or to reset settings)
-
- Parameters:
- default(bool):
- Reset to the default settings
- """
- self.settings.beginGroup('mainwindow')
- if default:
- # Delete settings group
- self.settings.remove('mainwindow')
-
- # -Load Settings-
- # None
-
- # -Done-
- self.settings.endGroup()
# -Widget Binds-
def pushButton_settings_clicked(self):
@@ -269,26 +102,33 @@ class MainWindow(QtWidgets.QWidget):
# Start seperation
self.app.threadpool.start(self.vocalRemoverRunnable)
- def pushButton_musicFiles_clicked(self):
+ def pushButton_delete_clicked(self):
"""
- Open music file selection dialog
+ Delete selected presets after asking for
+ confirmation
"""
- self.logger.info('Selecting Music Files...',
- indent_forwards=True)
- paths = QtWidgets.QFileDialog.getOpenFileNames(parent=self,
- caption='Select Music Files',
- dir=self.inputsDirectory,
- )[0]
-
- if not paths:
- # No files specified
- self.logger.info('No files selected!',)
- self.logger.indent_backwards()
+ selected_items = self.ui.listWidget_musicFiles.selectedItems()
+ if not len(selected_items):
return
- self.inputsDirectory = os.path.dirname(paths[0])
- self.add_to_input_paths(paths)
- self.logger.indent_backwards()
+ # Some paths already selected
+ msg = QtWidgets.QMessageBox()
+ msg.setWindowTitle(self.tr('Confirmation'))
+ msg.setIcon(QtWidgets.QMessageBox.Icon.Warning)
+ msg.setText(f'You will remove {len(selected_items)} items. Do you wish to continue?')
+ msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+ msg.setWindowFlag(Qt.WindowStaysOnTopHint)
+ val = msg.exec_()
+
+ if val == QtWidgets.QMessageBox.No:
+ # Cancel
+ return
+
+ removed_rows = [self.ui.listWidget_musicFiles.row(item) for item in selected_items]
+ self.inputPaths = [path for row, path in enumerate(self.inputPaths) if not row in removed_rows]
+
+ # -Update settings window-
+ self.update_window()
def stackedWidget_musicFiles_dragEnterEvent(self, event: QtGui.QDragEnterEvent):
"""
@@ -317,6 +157,31 @@ class MainWindow(QtWidgets.QWidget):
self.add_to_input_paths(inputPaths)
self.logger.indent_backwards()
+ def listWidget_musicFiles_itemDoubleClicked(self, item):
+ path = item.data(Qt.UserRole)
+ subprocess.Popen(r'explorer /select,"{0}"'.format(path.replace("/", "\\")))
+
+ def chooseMusicFiles(self):
+ """
+ Open music file selection dialog
+ """
+ self.logger.info('Selecting Music Files...',
+ indent_forwards=True)
+ paths = QtWidgets.QFileDialog.getOpenFileNames(parent=self,
+ caption='Select Music Files',
+ dir=self.inputsDirectory,
+ )[0]
+
+ if not paths:
+ # No files specified
+ self.logger.info('No files selected!',)
+ self.logger.indent_backwards()
+ return
+
+ self.inputsDirectory = os.path.dirname(paths[0])
+ self.add_to_input_paths(paths)
+ self.logger.indent_backwards()
+
def add_to_input_paths(self, paths: list):
"""
Checks if paths are already selected.
@@ -491,6 +356,177 @@ class MainWindow(QtWidgets.QWidget):
self.instrumentals_audioPlayer.setMedia(QtMultimedia.QMediaContent())
self.vocals_audioPlayer.setMedia(QtMultimedia.QMediaContent())
+ # -Initialization methods-
+ def setup_window(self):
+ """
+ Set up the window with binds, images, saved settings
+
+ (Only run right after window initialization of main and settings window)
+ """
+ def load_geometry():
+ """
+ Load the geometry of this window
+ """
+ # Window is centered on primary window
+ default_size = self.size()
+ default_pos = QtCore.QPoint()
+ default_pos.setX((self.app.primaryScreen().size().width() / 2) - default_size.width() / 2)
+ default_pos.setY((self.app.primaryScreen().size().height() / 2) - default_size.height() / 2)
+ # Get data
+ self.settings.beginGroup(self.__class__.__name__.lower())
+ size = self.settings.value('size',
+ default_size)
+ pos = self.settings.value('pos',
+ default_pos)
+ isMaximized = self.settings.value('isMaximized',
+ False,
+ type=bool)
+ self.settings.endGroup()
+ # Apply data
+ self.move(pos)
+ if isMaximized:
+ self.setWindowState(Qt.WindowMaximized)
+ else:
+ self.resize(size)
+
+ def load_images():
+ """
+ Load the images for this window and assign them to their widgets
+ """
+ # Settings button
+ self.settings_img = QtGui.QPixmap(ResourcePaths.images.settings)
+ self.ui.pushButton_settings.setIcon(self.settings_img)
+ self.ui.pushButton_settings.setIconSize(QtCore.QSize(25, 25))
+
+ def bind_widgets():
+ """
+ Bind the widgets here
+ """
+ # -Override binds-
+ # Music file drag & drop
+ self.ui.stackedWidget_musicFiles.dragEnterEvent = self.stackedWidget_musicFiles_dragEnterEvent
+ self.ui.stackedWidget_musicFiles.dropEvent = self.stackedWidget_musicFiles_dropEvent
+ # -Pushbuttons-
+ self.ui.pushButton_musicFiles.clicked.connect(self.chooseMusicFiles)
+ self.ui.pushButton_settings.clicked.connect(self.pushButton_settings_clicked)
+ self.ui.pushButton_seperate.clicked.connect(self.pushButton_seperate_clicked)
+ self.ui.pushButton_add.clicked.connect(self.chooseMusicFiles)
+ self.ui.pushButton_delete.clicked.connect(self.pushButton_delete_clicked)
+ # -ListWidget-
+ self.ui.listWidget_musicFiles.itemDoubleClicked.connect(self.listWidget_musicFiles_itemDoubleClicked)
+
+ def create_animation_objects():
+ """
+ Create the animation objects that are used
+ multiple times here
+ """
+ def style_progressbar():
+ """
+ Style pogressbar manually as when styled in Qt Designer
+ a bug occurs that prevents smooth animation of progressbar
+ """
+ self.ui.progressBar.setStyleSheet("""QProgressBar:horizontal {
+ border: 0px solid gray;
+ }
+ QProgressBar::chunk {
+ background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0.0795455 #368ADD, stop:1 #2180DF);
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ } """)
+ self.seperation_update_progress(0)
+ # -Progress Bar-
+ self.pbar_animation = QtCore.QPropertyAnimation(self.ui.progressBar, b"value",
+ parent=self)
+ # This is all to prevent the progressbar animation not working propertly
+ self.pbar_animation.setDuration(8)
+ self.pbar_animation.setStartValue(0)
+ self.pbar_animation.setEndValue(8)
+ self.pbar_animation.start()
+ self.pbar_animation.setDuration(500)
+ QtCore.QTimer.singleShot(1000, lambda: style_progressbar())
+ # -Settings Icon-
+
+ def rotate_settings_icon():
+ rotation = self.settings_ani.currentValue()
+ t = QtGui.QTransform()
+ t = t.rotate(rotation)
+ new_pixmap = self.settings_img.transformed(t, QtCore.Qt.FastTransformation)
+ xoffset = (new_pixmap.width() - self.settings_img.width()) / 2
+ yoffset = (new_pixmap.height() - self.settings_img.height()) / 2
+ new_pixmap = new_pixmap.copy(xoffset, yoffset, self.settings_img.width(), self.settings_img.height())
+ self.ui.pushButton_settings.setIcon(new_pixmap)
+
+ self.settings_ani = QtCore.QVariantAnimation(self)
+ self.settings_ani.setDuration(1750)
+ self.settings_ani.setEasingCurve(QtCore.QEasingCurve.OutBack)
+ self.settings_ani.setStartValue(0.0)
+ self.settings_ani.setEndValue(-180.0)
+ self.settings_ani.valueChanged.connect(rotate_settings_icon)
+
+ # -Before setup-
+ self.logger.info('Main -> Setting up',
+ indent_forwards=True)
+ # Load saved settings for widgets
+ self._load_data()
+ # Audio Players
+ self.instrumentals_audioPlayer = AudioPlayer(self.app,
+ self.ui.pushButton_play_instrumentals,
+ self.ui.horizontalSlider_instrumentals,
+ self.ui.pushButton_menu_instrumentals)
+ self.vocals_audioPlayer = AudioPlayer(self.app,
+ self.ui.pushButton_play_vocals,
+ self.ui.horizontalSlider_vocals,
+ self.ui.pushButton_menu_vocals)
+ # Temp func
+ self.tempAudioFilePaths = [os.path.join(ResourcePaths.tempDir, 'temp_instrumentals.wav'),
+ os.path.join(ResourcePaths.tempDir, 'temp_vocals.wav')]
+ self._deactivate_audio_players()
+
+ # -Setup-
+ load_geometry()
+ load_images()
+ bind_widgets()
+ create_animation_objects()
+ self.show()
+
+ # -After setup-
+ # Create WinTaskbar
+ self.winTaskbar = QWinTaskbarButton(self)
+ self.winTaskbar.setWindow(self.windowHandle())
+ self.winTaskbar_progress = self.winTaskbar.progress()
+ # Create instance
+ self.vocalRemoverRunnable = converter_v4.VocalRemoverWorker(logger=self.logger)
+ # Bind events
+ self.vocalRemoverRunnable.signals.start.connect(self.seperation_start)
+ self.vocalRemoverRunnable.signals.message.connect(self.seperation_write)
+ self.vocalRemoverRunnable.signals.progress.connect(self.seperation_update_progress)
+ self.vocalRemoverRunnable.signals.error.connect(self.seperation_error)
+ self.vocalRemoverRunnable.signals.finished.connect(self.seperation_finish)
+ # Late update
+ self.update_window()
+ self.logger.indent_backwards()
+
+ def _load_data(self, default: bool = False):
+ """
+ Load the data for this window
+
+ (Only run right after window initialization or to reset settings)
+
+ Parameters:
+ default(bool):
+ Reset to the default settings
+ """
+ self.settings.beginGroup('mainwindow')
+ if default:
+ # Delete settings group
+ self.settings.remove('mainwindow')
+
+ # -Load Settings-
+ # None
+
+ # -Done-
+ self.settings.endGroup()
+
# -Other Methods-
def update_window(self):
"""
@@ -519,9 +555,9 @@ class MainWindow(QtWidgets.QWidget):
"""
self.ui.listWidget_musicFiles.clear()
for path in self.inputPaths:
- item = QCustomListWidget()
- item.setTextUp(path)
+ item = QCustomListWidget(full_path=path)
widgetItem = QtWidgets.QListWidgetItem()
+ widgetItem.setData(Qt.UserRole, path)
widgetItem.setSizeHint(item.sizeHint())
self.ui.listWidget_musicFiles.addItem(widgetItem)
self.ui.listWidget_musicFiles.setItemWidget(widgetItem, item)
@@ -567,7 +603,7 @@ class MainWindow(QtWidgets.QWidget):
class QCustomListWidget(QtWidgets.QWidget):
- def __init__(self, parent=None):
+ def __init__(self, full_path: str, parent=None):
super(QCustomListWidget, self).__init__(parent)
self.textUpQLabel = QtWidgets.QLabel()
self.allQHBoxLayout = QtWidgets.QHBoxLayout()
@@ -582,6 +618,8 @@ class QCustomListWidget(QtWidgets.QWidget):
font-size: 15px;
background-color: none;
''')
+ self.full_path = full_path
+ self.setTextUp(os.path.basename(self.full_path))
def setTextUp(self, text):
self.textUpQLabel.setText(text)
diff --git a/src/windows/presetseditorwindow.py b/src/windows/presetseditorwindow.py
index 5ad2fdf..803003b 100644
--- a/src/windows/presetseditorwindow.py
+++ b/src/windows/presetseditorwindow.py
@@ -157,6 +157,9 @@ class PresetsEditorWindow(QtWidgets.QWidget):
confirmation
"""
selected_items = self.ui.listWidget_presets.selectedItems()
+ if not len(selected_items):
+ return
+
# Some paths already selected
msg = QtWidgets.QMessageBox()
msg.setWindowTitle(self.tr('Confirmation'))
@@ -170,7 +173,7 @@ class PresetsEditorWindow(QtWidgets.QWidget):
# Cancel
return
- for item in self.ui.listWidget_presets.selectedItems():
+ for item in selected_items:
row = self.ui.listWidget_presets.row(item)
self.ui.listWidget_presets.takeItem(row)
@@ -265,7 +268,7 @@ class PresetsEditorWindow(QtWidgets.QWidget):
presets = {}
for idx in range(self.ui.listWidget_presets.count()):
item = self.ui.listWidget_presets.item(idx)
- presets[item.text()] = item.data(Qt.UserRole)
+ presets[item.text()] = item.data(Qt.UserRole).copy()
return presets
@@ -277,6 +280,7 @@ class PresetsEditorWindow(QtWidgets.QWidget):
if name in presets:
return presets[name]
else:
+ self.logger.warning(f'No preset with name: "{name}" Available preset names: "{presets.keys()}"')
return {}
# -Overriden methods-
diff --git a/src/windows/settingswindow.py b/src/windows/settingswindow.py
index dba420a..4dc86df 100644
--- a/src/windows/settingswindow.py
+++ b/src/windows/settingswindow.py
@@ -41,7 +41,6 @@ class SettingsWindow(QtWidgets.QWidget):
self.setWindowIcon(QtGui.QIcon(ResourcePaths.images.settings))
# -Other Variables-
- self.suppress_settings_change_event = False
self.menu_update_methods = {
0: self.update_page_seperationSettings,
1: self.update_page_shortcuts,
@@ -52,6 +51,7 @@ class SettingsWindow(QtWidgets.QWidget):
self.exportDirectory = self.settings.value('user/exportDirectory',
const.DEFAULT_SETTINGS['exportDirectory'],
type=str)
+ self.search_for_preset = True
# -Widget Binds-
def pushButton_clearCommand_clicked(self):
@@ -125,12 +125,14 @@ class SettingsWindow(QtWidgets.QWidget):
"""
Changed preset
"""
+ self.search_for_preset = False
name = self.ui.comboBox_presets.currentText()
settings = self.app.windows['presetsEditor'].get_settings(name)
for json_key in list(settings.keys()):
widget_objectName = const.JSON_TO_NAME[json_key]
settings[widget_objectName] = settings.pop(json_key)
self.settingsManager.set_settings(settings)
+ self.search_for_preset = True
def frame_export_dragEnterEvent(self, event: QtGui.QDragEnterEvent):
"""
@@ -187,11 +189,22 @@ class SettingsWindow(QtWidgets.QWidget):
self.logger.indent_backwards()
def settings_changed(self):
- if (self.ui.comboBox_presets.currentText() and
- not self.suppress_settings_change_event):
- self.ui.comboBox_presets.setCurrentText('')
+ if self.search_for_preset:
+ current_settings = self.settingsManager.get_settings(0)
+ presets: dict = self.app.windows['presetsEditor'].get_presets()
+
+ for preset_name, settings in presets.items():
+ for json_key, value in settings.items():
+ if (current_settings[const.JSON_TO_NAME[json_key]] != value):
+ break
+ else:
+ self.ui.comboBox_presets.setCurrentText(preset_name)
+ break
+ else:
+ self.ui.comboBox_presets.setCurrentIndex(0)
# -Window Setup Methods-
+
def setup_window(self):
"""
Set up the window with binds, images, saved settings
@@ -272,7 +285,7 @@ class SettingsWindow(QtWidgets.QWidget):
if isinstance(widget, QtWidgets.QCheckBox):
widget.stateChanged.connect(self.settings_changed)
elif isinstance(widget, QtWidgets.QComboBox):
- widget.currentIndexChanged.connect(self.settings_changed)
+ widget.currentTextChanged.connect(self.settings_changed)
elif isinstance(widget, QtWidgets.QLineEdit):
widget.textChanged.connect(self.settings_changed)
elif (isinstance(widget, QtWidgets.QDoubleSpinBox) or
@@ -323,6 +336,7 @@ class SettingsWindow(QtWidgets.QWidget):
"""
# -Before setup-
+ self.search_for_preset = False
# Load saved settings for widgets
self.settingsManager.load_window()
# Update available model lists
@@ -357,6 +371,8 @@ class SettingsWindow(QtWidgets.QWidget):
# Load menu (Preferences)
self.update_window()
self.menu_loadPage(0, True)
+ self.search_for_preset = True
+ self.settings_changed()
self.logger.indent_backwards()
def load_window(self):
@@ -400,7 +416,7 @@ class SettingsWindow(QtWidgets.QWidget):
self.ui.comboBox_presets.blockSignals(True)
last_text = self.ui.comboBox_presets.currentText()
self.ui.comboBox_presets.clear()
- self.ui.comboBox_presets.addItem('')
+ self.ui.comboBox_presets.addItem('Custom')
for idx in range(self.app.windows['presetsEditor'].ui.listWidget_presets.count()):
# Loop through every preset in the list on the window
# Get item by index
@@ -412,6 +428,7 @@ class SettingsWindow(QtWidgets.QWidget):
if text == last_text:
self.ui.comboBox_presets.setCurrentText(text)
self.ui.comboBox_presets.blockSignals(False)
+ self.settings_changed()
self.logger.indent_backwards()
@@ -647,7 +664,7 @@ class SettingsManager:
self.save_widgets[2] = customization_widgets
self.save_widgets[3] = preferences_widgets
- def get_settings(self, page_idx: Optional[int] = None) -> Dict[str, Union[bool, str]]:
+ def get_settings(self, page_idx: Optional[int] = None) -> Dict[str, Union[bool, str, float]]:
"""Obtain states of the widgets
Args:
@@ -665,7 +682,7 @@ class SettingsManager:
TypeError: Invalid widget type in the widgets (has to be either: QCheckBox, QRadioButton, QLineEdit or QComboBox)
Returns:
- Dict[str, Union[bool, str]]: Widget states
+ Dict[str, Union[bool, str, float]]: Widget states
Key - Widget object name
Value - State of the widget
"""
@@ -693,7 +710,7 @@ class SettingsManager:
return settings
- def set_settings(self, settings: Dict[str, Union[bool, str]]):
+ def set_settings(self, settings: Dict[str, Union[bool, str, float]]):
"""Update states of the widgets
The given dict's key should be the widgets object name
@@ -714,12 +731,11 @@ class SettingsManager:
Args:
- settings (Dict[str, Union[bool, str]]): States of the widgets to update
+ settings (Dict[str, Union[bool, str, float]]): States of the widgets to update
Raises:
TypeError: Invalid widget type in the widgets (has to be either: QCheckBox, QRadioButton, QLineEdit or QComboBox)
"""
- self.win.suppress_settings_change_event = True
for widget_objectName, value in settings.items():
# Get widget
widget = self.win.findChild(QtCore.QObject, widget_objectName)
@@ -746,7 +762,6 @@ class SettingsManager:
widget.setValue(value)
else:
raise TypeError('Invalid widget type that is not supported!\nWidget: ', widget)
- self.win.suppress_settings_change_event = False
self.win.update_window()
def load_window(self):
@@ -757,7 +772,6 @@ class SettingsManager:
"""
# Before
self.win.logger.info('Settings: Loading window')
-
# -Load states-
self.win.settings.beginGroup('settingswindow')
for widget in self.get_widgets():
@@ -803,6 +817,7 @@ class SettingsManager:
widget.setValue(value)
else:
raise TypeError('Invalid widget type that is not supported!\nWidget: ', widget)
+
self.win.settings.endGroup()
def save_window(self):
diff --git a/ui_files/mainwindow.ui b/ui_files/mainwindow.ui
index 9a1807d..d332baa 100644
--- a/ui_files/mainwindow.ui
+++ b/ui_files/mainwindow.ui
@@ -6,7 +6,7 @@
0
0
- 911
+ 947
559
@@ -22,7 +22,7 @@
-
+
0
@@ -33,7 +33,7 @@
0
- 5
+ 0
0
@@ -137,7 +137,7 @@
1
- 0
+ 1
@@ -181,10 +181,7 @@
-
-
- 0
-
+
0
@@ -205,12 +202,6 @@
0
-
-
- 16777215
- 16777215
-
-
QFrame::NoFrame
@@ -227,7 +218,7 @@
true
- QAbstractItemView::NoSelection
+ QAbstractItemView::ExtendedSelection
true
@@ -237,6 +228,80 @@
+ -
+
+
+
+ 35
+ 16777215
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 35
+ 35
+
+
+
+
+ 35
+ 35
+
+
+
+ PointingHandCursor
+
+
+ +
+
+
+
+ -
+
+
+
+ 35
+ 35
+
+
+
+
+ 35
+ 35
+
+
+
+ PointingHandCursor
+
+
+ -
+
+
+
+
+
+
@@ -662,6 +727,9 @@
50
+
+ PointingHandCursor
+
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
@@ -691,6 +759,9 @@ border-bottom-right-radius: 0px;
50
+
+ PointingHandCursor
+
border-left: none;
border-top-left-radius: 0px;
diff --git a/ui_files/settingswindow.ui b/ui_files/settingswindow.ui
index 25d5be6..4518eb9 100644
--- a/ui_files/settingswindow.ui
+++ b/ui_files/settingswindow.ui
@@ -962,8 +962,8 @@
0
0
- 541
- 510
+ 53
+ 35
@@ -1039,7 +1039,7 @@
0
0
- 541
+ 630
510
@@ -1164,7 +1164,7 @@
0
0
- 600
+ 630
510