mirror of
https://github.com/Anjok07/ultimatevocalremovergui.git
synced 2025-02-22 05:09:35 +01:00
Finished polish
This commit is contained in:
parent
14799fdeb5
commit
3a150f7f23
@ -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)
|
||||
|
@ -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'])
|
||||
|
@ -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;
|
||||
|
@ -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"] {
|
||||
|
@ -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;
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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-
|
||||
|
@ -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):
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user