Finished polish

This commit is contained in:
Dilan Boskan 2021-06-09 22:52:36 +02:00
parent 14799fdeb5
commit 3a150f7f23
11 changed files with 1244 additions and 432 deletions

View File

@ -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)

View File

@ -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'])

View File

@ -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;

View File

@ -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"] {

View File

@ -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;
}

View File

@ -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)

View File

@ -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)

View File

@ -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-

View File

@ -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):

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>911</width>
<width>947</width>
<height>559</height>
</rect>
</property>
@ -22,7 +22,7 @@
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="3,2">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="3,0">
<property name="spacing">
<number>0</number>
</property>
@ -33,7 +33,7 @@
<number>0</number>
</property>
<property name="rightMargin">
<number>5</number>
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
@ -137,7 +137,7 @@
<number>1</number>
</property>
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="page_select">
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -181,10 +181,7 @@
</layout>
</widget>
<widget class="QWidget" name="page_display">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
@ -205,12 +202,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
@ -227,7 +218,7 @@
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
@ -237,6 +228,80 @@
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame_7">
<property name="maximumSize">
<size>
<width>35</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="pushButton_add">
<property name="minimumSize">
<size>
<width>35</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>35</width>
<height>35</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string notr="true">+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_delete">
<property name="minimumSize">
<size>
<width>35</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>35</width>
<height>35</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string notr="true">-</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
@ -662,6 +727,9 @@
<height>50</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">border-top-right-radius: 0px;
border-bottom-right-radius: 0px;</string>
@ -691,6 +759,9 @@ border-bottom-right-radius: 0px;</string>
<height>50</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">border-left: none;
border-top-left-radius: 0px;

View File

@ -962,8 +962,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>541</width>
<height>510</height>
<width>53</width>
<height>35</height>
</rect>
</property>
<property name="styleSheet">
@ -1039,7 +1039,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>541</width>
<width>630</width>
<height>510</height>
</rect>
</property>
@ -1164,7 +1164,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<width>630</width>
<height>510</height>
</rect>
</property>