diff --git a/.env b/.env
new file mode 100644
index 0000000000000000000000000000000000000000..78ea8fbe1604c33e3976865d8977ae0fd073292e
--- /dev/null
+++ b/.env
@@ -0,0 +1,8 @@
+OPENBLAS_NUM_THREADS = 1
+no_proxy = localhost, 127.0.0.1, ::1
+
+# You can change the location of the model, etc. by changing here
+weight_root = weights
+weight_uvr5_root = uvr5_weights
+index_root = logs
+rmvpe_root = assets/rmvpe
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..8f5dd3e8e3be92c6c0cf9d9a7b8c7fe681c90238
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,36 @@
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.arrow filter=lfs diff=lfs merge=lfs -text
+*.bin filter=lfs diff=lfs merge=lfs -text
+*.bz2 filter=lfs diff=lfs merge=lfs -text
+*.ckpt filter=lfs diff=lfs merge=lfs -text
+*.ftz filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.h5 filter=lfs diff=lfs merge=lfs -text
+*.joblib filter=lfs diff=lfs merge=lfs -text
+*.lfs.* filter=lfs diff=lfs merge=lfs -text
+*.mlmodel filter=lfs diff=lfs merge=lfs -text
+*.model filter=lfs diff=lfs merge=lfs -text
+*.msgpack filter=lfs diff=lfs merge=lfs -text
+*.npy filter=lfs diff=lfs merge=lfs -text
+*.npz filter=lfs diff=lfs merge=lfs -text
+*.onnx filter=lfs diff=lfs merge=lfs -text
+*.ot filter=lfs diff=lfs merge=lfs -text
+*.parquet filter=lfs diff=lfs merge=lfs -text
+*.pb filter=lfs diff=lfs merge=lfs -text
+*.pickle filter=lfs diff=lfs merge=lfs -text
+*.pkl filter=lfs diff=lfs merge=lfs -text
+*.pt filter=lfs diff=lfs merge=lfs -text
+*.pth filter=lfs diff=lfs merge=lfs -text
+*.rar filter=lfs diff=lfs merge=lfs -text
+*.safetensors filter=lfs diff=lfs merge=lfs -text
+saved_model/**/* filter=lfs diff=lfs merge=lfs -text
+*.tar.* filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.tflite filter=lfs diff=lfs merge=lfs -text
+*.tgz filter=lfs diff=lfs merge=lfs -text
+*.wasm filter=lfs diff=lfs merge=lfs -text
+*.xz filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+*.zst filter=lfs diff=lfs merge=lfs -text
+*tfevents* filter=lfs diff=lfs merge=lfs -text
+stftpitchshift filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1cc60801f6d12ad4d01ccb1786860d8583c780f6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,49 @@
+.DS_Store
+__pycache__
+/TEMP
+/DATASETS
+/RUNTIME
+*.pyd
+hubert_base.pt
+.venv
+alexforkINSTALL.bat
+Changelog_CN.md
+Changelog_EN.md
+Changelog_KO.md
+difdep.py
+EasierGUI.py
+envfilescheck.bat
+export_onnx.py
+.vscode/
+export_onnx_old.py
+ffmpeg.exe
+ffprobe.exe
+Fixes/Launch_Tensorboard.bat
+Fixes/LOCAL_CREPE_FIX.bat
+Fixes/local_fixes.py
+Fixes/tensor-launch.py
+gui.py
+infer-web — backup.py
+infer-webbackup.py
+install_easy_dependencies.py
+install_easyGUI.bat
+installstft.bat
+Launch_Tensorboard.bat
+listdepend.bat
+LOCAL_CREPE_FIX.bat
+local_fixes.py
+oldinfer.py
+onnx_inference_demo.py
+Praat.exe
+requirementsNEW.txt
+rmvpe.pt
+rmvpe.onnx
+run_easiergui.bat
+tensor-launch.py
+values1.json
+使用需遵守的协议-LICENSE.txt
+!logs/
+
+logs/*
+logs/mute/0_gt_wavs/mute40k.spec.pt
+!logs/mute/
\ No newline at end of file
diff --git a/Applio-RVC-Fork/utils/README.md b/Applio-RVC-Fork/utils/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..fb45a36b5909585aa964f2033762ee59b55526b0
--- /dev/null
+++ b/Applio-RVC-Fork/utils/README.md
@@ -0,0 +1,6 @@
+# External Colab Code
+Code used to make Google Colab work correctly
+- Repo link: https://github.com/IAHispano/Applio-RVC-Fork/
+
+Thanks to https://github.com/kalomaze/externalcolabcode
+
diff --git a/Applio-RVC-Fork/utils/backups.py b/Applio-RVC-Fork/utils/backups.py
new file mode 100644
index 0000000000000000000000000000000000000000..b814f8184792e80e2324685436053d61487110b1
--- /dev/null
+++ b/Applio-RVC-Fork/utils/backups.py
@@ -0,0 +1,141 @@
+import os
+import shutil
+import hashlib
+import time
+import base64
+
+
+
+
+LOGS_FOLDER = '/content/Applio-RVC-Fork/logs'
+WEIGHTS_FOLDER = '/content/Applio-RVC-Fork/weights'
+GOOGLE_DRIVE_PATH = '/content/drive/MyDrive/RVC_Backup'
+
+def import_google_drive_backup():
+ print("Importing Google Drive backup...")
+ weights_exist = False
+ for root, dirs, files in os.walk(GOOGLE_DRIVE_PATH):
+ for filename in files:
+ filepath = os.path.join(root, filename)
+ if os.path.isfile(filepath) and not filepath.startswith(os.path.join(GOOGLE_DRIVE_PATH, 'weights')):
+ backup_filepath = os.path.join(LOGS_FOLDER, os.path.relpath(filepath, GOOGLE_DRIVE_PATH))
+ backup_folderpath = os.path.dirname(backup_filepath)
+ if not os.path.exists(backup_folderpath):
+ os.makedirs(backup_folderpath)
+ print(f'Created backup folder: {backup_folderpath}', flush=True)
+ shutil.copy2(filepath, backup_filepath) # copy file with metadata
+ print(f'Imported file from Google Drive backup: {filename}')
+ elif filepath.startswith(os.path.join(GOOGLE_DRIVE_PATH, 'weights')) and filename.endswith('.pth'):
+ weights_exist = True
+ weights_filepath = os.path.join(WEIGHTS_FOLDER, os.path.relpath(filepath, os.path.join(GOOGLE_DRIVE_PATH, 'weights')))
+ weights_folderpath = os.path.dirname(weights_filepath)
+ if not os.path.exists(weights_folderpath):
+ os.makedirs(weights_folderpath)
+ print(f'Created weights folder: {weights_folderpath}', flush=True)
+ shutil.copy2(filepath, weights_filepath) # copy file with metadata
+ print(f'Imported file from weights: {filename}')
+ if weights_exist:
+ print("Copied weights from Google Drive backup to local weights folder.")
+ else:
+ print("No weights found in Google Drive backup.")
+ print("Google Drive backup import completed.")
+
+def get_md5_hash(file_path):
+ hash_md5 = hashlib.md5()
+ with open(file_path, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ hash_md5.update(chunk)
+ return hash_md5.hexdigest()
+
+def copy_weights_folder_to_drive():
+ destination_folder = os.path.join(GOOGLE_DRIVE_PATH, 'weights')
+ try:
+ if not os.path.exists(destination_folder):
+ os.makedirs(destination_folder)
+
+ num_copied = 0
+ for filename in os.listdir(WEIGHTS_FOLDER):
+ if filename.endswith('.pth'):
+ source_file = os.path.join(WEIGHTS_FOLDER, filename)
+ destination_file = os.path.join(destination_folder, filename)
+ if not os.path.exists(destination_file):
+ shutil.copy2(source_file, destination_file)
+ num_copied += 1
+ print(f"Copied {filename} to Google Drive!")
+
+ if num_copied == 0:
+ print("No new finished models found for copying.")
+ else:
+ print(f"Finished copying {num_copied} files to Google Drive!")
+
+ except Exception as e:
+ print(f"An error occurred while copying weights: {str(e)}")
+ # You can log the error or take appropriate actions here.
+
+def backup_files():
+ print("\nStarting backup loop...")
+ last_backup_timestamps_path = os.path.join(LOGS_FOLDER, 'last_backup_timestamps.txt')
+ fully_updated = False # boolean to track if all files are up to date
+
+ while True:
+ try:
+ updated = False # flag to check if any files were updated
+ last_backup_timestamps = {}
+
+ try:
+ with open(last_backup_timestamps_path, 'r') as f:
+ last_backup_timestamps = dict(line.strip().split(':') for line in f)
+ except FileNotFoundError:
+ pass # File does not exist yet, which is fine
+
+ for root, dirs, files in os.walk(LOGS_FOLDER):
+ for filename in files:
+ if filename != 'last_backup_timestamps.txt':
+ filepath = os.path.join(root, filename)
+ if os.path.isfile(filepath):
+ backup_filepath = os.path.join(GOOGLE_DRIVE_PATH, os.path.relpath(filepath, LOGS_FOLDER))
+ backup_folderpath = os.path.dirname(backup_filepath)
+ if not os.path.exists(backup_folderpath):
+ os.makedirs(backup_folderpath)
+ print(f'Created backup folder: {backup_folderpath}', flush=True)
+ # check if file has changed since last backup
+ last_backup_timestamp = last_backup_timestamps.get(filepath)
+ current_timestamp = os.path.getmtime(filepath)
+ if last_backup_timestamp is None or float(last_backup_timestamp) < current_timestamp:
+ shutil.copy2(filepath, backup_filepath) # copy file with metadata
+ last_backup_timestamps[filepath] = str(current_timestamp) # update last backup timestamp
+ if last_backup_timestamp is None:
+ print(f'Backed up file: {filename}')
+ else:
+ print(f'Updating backed up file: {filename}')
+ updated = True
+ fully_updated = False # if a file is updated, all files are not up to date
+
+ # check if any files were deleted in Colab and delete them from the backup drive
+ for filepath in list(last_backup_timestamps.keys()):
+ if not os.path.exists(filepath):
+ backup_filepath = os.path.join(GOOGLE_DRIVE_PATH, os.path.relpath(filepath, LOGS_FOLDER))
+ if os.path.exists(backup_filepath):
+ os.remove(backup_filepath)
+ print(f'Deleted file: {filepath}')
+ del last_backup_timestamps[filepath]
+ updated = True
+ fully_updated = False # if a file is deleted, all files are not up to date
+
+ if not updated and not fully_updated:
+ print("Files are up to date.")
+ fully_updated = True # if all files are up to date, set the boolean to True
+ copy_weights_folder_to_drive()
+ sleep_time = 15
+ else:
+ sleep_time = 0.1
+
+ with open(last_backup_timestamps_path, 'w') as f:
+ for filepath, timestamp in last_backup_timestamps.items():
+ f.write(f'{filepath}:{timestamp}\n')
+
+ time.sleep(sleep_time) # wait for 15 seconds before checking again, or 0.1s if not fully up to date to speed up backups
+
+ except Exception as e:
+ print(f"An error occurred: {str(e)}")
+ # You can log the error or take appropriate actions here.
diff --git a/Applio-RVC-Fork/utils/backups_test.py b/Applio-RVC-Fork/utils/backups_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3edf15811b5035ee82f21e54e87b7e87ce413eb
--- /dev/null
+++ b/Applio-RVC-Fork/utils/backups_test.py
@@ -0,0 +1,138 @@
+
+import os
+import shutil
+import hashlib
+import time
+
+LOGS_FOLDER = '/content/Applio-RVC-Fork/logs'
+WEIGHTS_FOLDER = '/content/Applio-RVC-Fork/weights'
+GOOGLE_DRIVE_PATH = '/content/drive/MyDrive/RVC_Backup'
+
+def import_google_drive_backup():
+ print("Importing Google Drive backup...")
+ GOOGLE_DRIVE_PATH = '/content/drive/MyDrive/RVC_Backup' # change this to your Google Drive path
+ LOGS_FOLDER = '/content/Applio-RVC-Fork/logs'
+ WEIGHTS_FOLDER = '/content/Applio-RVC-Fork/weights'
+ weights_exist = False
+ files_to_copy = []
+ weights_to_copy = []
+
+ def handle_files(root, files, is_weight_files=False):
+ for filename in files:
+ filepath = os.path.join(root, filename)
+ if filename.endswith('.pth') and is_weight_files:
+ weights_exist = True
+ backup_filepath = os.path.join(WEIGHTS_FOLDER, os.path.relpath(filepath, GOOGLE_DRIVE_PATH))
+ else:
+ backup_filepath = os.path.join(LOGS_FOLDER, os.path.relpath(filepath, GOOGLE_DRIVE_PATH))
+ backup_folderpath = os.path.dirname(backup_filepath)
+ if not os.path.exists(backup_folderpath):
+ os.makedirs(backup_folderpath)
+ print(f'Created folder: {backup_folderpath}', flush=True)
+ if is_weight_files:
+ weights_to_copy.append((filepath, backup_filepath))
+ else:
+ files_to_copy.append((filepath, backup_filepath))
+
+ for root, dirs, files in os.walk(os.path.join(GOOGLE_DRIVE_PATH, 'logs')):
+ handle_files(root, files)
+
+ for root, dirs, files in os.walk(os.path.join(GOOGLE_DRIVE_PATH, 'weights')):
+ handle_files(root, files, True)
+
+ # Copy files in batches
+ total_files = len(files_to_copy)
+ start_time = time.time()
+ for i, (source, dest) in enumerate(files_to_copy, start=1):
+ with open(source, 'rb') as src, open(dest, 'wb') as dst:
+ shutil.copyfileobj(src, dst, 1024*1024) # 1MB buffer size
+ # Report progress every 5 seconds or after every 100 files, whichever is less frequent
+ if time.time() - start_time > 5 or i % 100 == 0:
+ print(f'\rCopying file {i} of {total_files} ({i * 100 / total_files:.2f}%)', end="")
+ start_time = time.time()
+ print(f'\nImported {len(files_to_copy)} files from Google Drive backup')
+
+ # Copy weights in batches
+ total_weights = len(weights_to_copy)
+ start_time = time.time()
+ for i, (source, dest) in enumerate(weights_to_copy, start=1):
+ with open(source, 'rb') as src, open(dest, 'wb') as dst:
+ shutil.copyfileobj(src, dst, 1024*1024) # 1MB buffer size
+ # Report progress every 5 seconds or after every 100 files, whichever is less frequent
+ if time.time() - start_time > 5 or i % 100 == 0:
+ print(f'\rCopying weight file {i} of {total_weights} ({i * 100 / total_weights:.2f}%)', end="")
+ start_time = time.time()
+ if weights_exist:
+ print(f'\nImported {len(weights_to_copy)} weight files')
+ print("Copied weights from Google Drive backup to local weights folder.")
+ else:
+ print("\nNo weights found in Google Drive backup.")
+ print("Google Drive backup import completed.")
+
+def backup_files():
+ print("\n Starting backup loop...")
+ last_backup_timestamps_path = os.path.join(LOGS_FOLDER, 'last_backup_timestamps.txt')
+ fully_updated = False # boolean to track if all files are up to date
+ try:
+ with open(last_backup_timestamps_path, 'r') as f:
+ last_backup_timestamps = dict(line.strip().split(':') for line in f)
+ except:
+ last_backup_timestamps = {}
+
+ while True:
+ updated = False
+ files_to_copy = []
+ files_to_delete = []
+
+ for root, dirs, files in os.walk(LOGS_FOLDER):
+ for filename in files:
+ if filename != 'last_backup_timestamps.txt':
+ filepath = os.path.join(root, filename)
+ if os.path.isfile(filepath):
+ backup_filepath = os.path.join(GOOGLE_DRIVE_PATH, os.path.relpath(filepath, LOGS_FOLDER))
+ backup_folderpath = os.path.dirname(backup_filepath)
+
+ if not os.path.exists(backup_folderpath):
+ os.makedirs(backup_folderpath)
+ print(f'Created backup folder: {backup_folderpath}', flush=True)
+
+ # check if file has changed since last backup
+ last_backup_timestamp = last_backup_timestamps.get(filepath)
+ current_timestamp = os.path.getmtime(filepath)
+ if last_backup_timestamp is None or float(last_backup_timestamp) < current_timestamp:
+ files_to_copy.append((filepath, backup_filepath)) # add to list of files to copy
+ last_backup_timestamps[filepath] = str(current_timestamp) # update last backup timestamp
+ updated = True
+ fully_updated = False # if a file is updated, all files are not up to date
+
+ # check if any files were deleted in Colab and delete them from the backup drive
+ for filepath in list(last_backup_timestamps.keys()):
+ if not os.path.exists(filepath):
+ backup_filepath = os.path.join(GOOGLE_DRIVE_PATH, os.path.relpath(filepath, LOGS_FOLDER))
+ if os.path.exists(backup_filepath):
+ files_to_delete.append(backup_filepath) # add to list of files to delete
+ del last_backup_timestamps[filepath]
+ updated = True
+ fully_updated = False # if a file is deleted, all files are not up to date
+
+ # Copy files in batches
+ if files_to_copy:
+ for source, dest in files_to_copy:
+ shutil.copy2(source, dest)
+ print(f'Copied or updated {len(files_to_copy)} files')
+
+ # Delete files in batches
+ if files_to_delete:
+ for file in files_to_delete:
+ os.remove(file)
+ print(f'Deleted {len(files_to_delete)} files')
+
+ if not updated and not fully_updated:
+ print("Files are up to date.")
+ fully_updated = True # if all files are up to date, set the boolean to True
+ copy_weights_folder_to_drive()
+
+ with open(last_backup_timestamps_path, 'w') as f:
+ for filepath, timestamp in last_backup_timestamps.items():
+ f.write(f'{filepath}:{timestamp}\n')
+ time.sleep(15) # wait for 15 seconds before checking again
diff --git a/Applio-RVC-Fork/utils/clonerepo_experimental.py b/Applio-RVC-Fork/utils/clonerepo_experimental.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0ae02648c1307562cf48033908edcf2996db5e2
--- /dev/null
+++ b/Applio-RVC-Fork/utils/clonerepo_experimental.py
@@ -0,0 +1,253 @@
+import os
+import subprocess
+import shutil
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from tqdm.notebook import tqdm
+from pathlib import Path
+import requests
+
+def run_script():
+ def run_cmd(cmd):
+ process = subprocess.run(cmd, shell=True, check=True, text=True)
+ return process.stdout
+
+ # Change the current directory to /content/
+ os.chdir('/content/')
+ print("Changing dir to /content/")
+
+ # Your function to edit the file
+ def edit_file(file_path):
+ temp_file_path = "/tmp/temp_file.py"
+ changes_made = False
+ with open(file_path, "r") as file, open(temp_file_path, "w") as temp_file:
+ previous_line = ""
+ second_previous_line = ""
+ for line in file:
+ new_line = line.replace("value=160", "value=128")
+ if new_line != line:
+ print("Replaced 'value=160' with 'value=128'")
+ changes_made = True
+ line = new_line
+
+ new_line = line.replace("crepe hop length: 160", "crepe hop length: 128")
+ if new_line != line:
+ print("Replaced 'crepe hop length: 160' with 'crepe hop length: 128'")
+ changes_made = True
+ line = new_line
+
+ new_line = line.replace("value=0.88", "value=0.75")
+ if new_line != line:
+ print("Replaced 'value=0.88' with 'value=0.75'")
+ changes_made = True
+ line = new_line
+
+ if "label=i18n(\"输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络\")" in previous_line and "value=1," in line:
+ new_line = line.replace("value=1,", "value=0.25,")
+ if new_line != line:
+ print("Replaced 'value=1,' with 'value=0.25,' based on the condition")
+ changes_made = True
+ line = new_line
+
+ if "label=i18n(\"总训练轮数total_epoch\")" in previous_line and "value=20," in line:
+ new_line = line.replace("value=20,", "value=500,")
+ if new_line != line:
+ print("Replaced 'value=20,' with 'value=500,' based on the condition for DEFAULT EPOCH")
+ changes_made = True
+ line = new_line
+
+ if 'choices=["pm", "harvest", "dio", "crepe", "crepe-tiny", "mangio-crepe", "mangio-crepe-tiny"], # Fork Feature. Add Crepe-Tiny' in previous_line:
+ if 'value="pm",' in line:
+ new_line = line.replace('value="pm",', 'value="mangio-crepe",')
+ if new_line != line:
+ print("Replaced 'value=\"pm\",' with 'value=\"mangio-crepe\",' based on the condition")
+ changes_made = True
+ line = new_line
+
+ new_line = line.replace('label=i18n("输入训练文件夹路径"), value="E:\\\\语音音频+标注\\\\米津玄师\\\\src"', 'label=i18n("输入训练文件夹路径"), value="/content/dataset/"')
+ if new_line != line:
+ print("Replaced 'label=i18n(\"输入训练文件夹路径\"), value=\"E:\\\\语音音频+标注\\\\米津玄师\\\\src\"' with 'label=i18n(\"输入训练文件夹路径\"), value=\"/content/dataset/\"'")
+ changes_made = True
+ line = new_line
+
+ if 'label=i18n("是否仅保存最新的ckpt文件以节省硬盘空间"),' in second_previous_line:
+ if 'value=i18n("否"),' in line:
+ new_line = line.replace('value=i18n("否"),', 'value=i18n("是"),')
+ if new_line != line:
+ print("Replaced 'value=i18n(\"否\"),' with 'value=i18n(\"是\"),' based on the condition for SAVE ONLY LATEST")
+ changes_made = True
+ line = new_line
+
+ if 'label=i18n("是否在每次保存时间点将最终小模型保存至weights文件夹"),' in second_previous_line:
+ if 'value=i18n("否"),' in line:
+ new_line = line.replace('value=i18n("否"),', 'value=i18n("是"),')
+ if new_line != line:
+ print("Replaced 'value=i18n(\"否\"),' with 'value=i18n(\"是\"),' based on the condition for SAVE SMALL WEIGHTS")
+ changes_made = True
+ line = new_line
+
+ temp_file.write(line)
+ second_previous_line = previous_line
+ previous_line = line
+
+ # After finished, we replace the original file with the temp one
+ import shutil
+ shutil.move(temp_file_path, file_path)
+
+ if changes_made:
+ print("Changes made and file saved successfully.")
+ else:
+ print("No changes were needed.")
+
+ # Define the repo path
+ repo_path = '/content/Applio-RVC-Fork'
+
+ def copy_all_files_in_directory(src_dir, dest_dir):
+ # Iterate over all files in source directory
+ for item in Path(src_dir).glob('*'):
+ if item.is_file():
+ # Copy each file to destination directory
+ shutil.copy(item, dest_dir)
+ else:
+ # If it's a directory, make a new directory in the destination and copy the files recursively
+ new_dest = Path(dest_dir) / item.name
+ new_dest.mkdir(exist_ok=True)
+ copy_all_files_in_directory(str(item), str(new_dest))
+
+ def clone_and_copy_repo(repo_path):
+ # New repository link
+ new_repo_link = "https://github.com/IAHispano/Applio-RVC-Fork/"
+ # Temporary path to clone the repository
+ temp_repo_path = "/content/temp_Applio-RVC-Fork"
+ # New folder name
+ new_folder_name = "Applio-RVC-Fork"
+
+ # Clone the latest code from the new repository to a temporary location
+ run_cmd(f"git clone {new_repo_link} {temp_repo_path}")
+ os.chdir(temp_repo_path)
+
+ run_cmd(f"git checkout 3fa4dad3d8961e5ca2522e9e12c0b4ddb71ad402")
+ run_cmd(f"git checkout f9e606c279cb49420597519b0a83b92be81e42e4")
+ run_cmd(f"git checkout 9e305588844c5442d58add1061b29beeca89d679")
+ run_cmd(f"git checkout bf92dc1eb54b4f28d6396a4d1820a25896cc9af8")
+ run_cmd(f"git checkout c3810e197d3cb98039973b2f723edf967ecd9e61")
+ run_cmd(f"git checkout a33159efd134c2413b0afe26a76b7dc87926d2de")
+ run_cmd(f"git checkout 24e251fb62c662e39ac5cf9253cc65deb9be94ec")
+ run_cmd(f"git checkout ad5667d3017e93232dba85969cddac1322ba2902")
+ run_cmd(f"git checkout ce9715392cf52dd5a0e18e00d1b5e408f08dbf27")
+ run_cmd(f"git checkout 7c7da3f2ac68f3bd8f3ad5ca5c700f18ab9f90eb")
+ run_cmd(f"git checkout 4ac395eab101955e8960b50d772c26f592161764")
+ run_cmd(f"git checkout b15b358702294c7375761584e5276c811ffab5e8")
+ run_cmd(f"git checkout 1501793dc490982db9aca84a50647764caa66e51")
+ run_cmd(f"git checkout 21f7faf57219c75e6ba837062350391a803e9ae2")
+ run_cmd(f"git checkout b5eb689fbc409b49f065a431817f822f554cebe7")
+ run_cmd(f"git checkout 7e02fae1ebf24cb151bf6cbe787d06734aa65862")
+ run_cmd(f"git checkout 6aea5ea18ed0b9a1e03fa5d268d6bc3c616672a9")
+ run_cmd(f"git checkout f0f9b25717e59116473fb42bd7f9252cfc32b398")
+ run_cmd(f"git checkout b394de424088a81fc081224bc27338a8651ad3b2")
+ run_cmd(f"git checkout f1999406a88b80c965d2082340f5ea2bfa9ab67a")
+ run_cmd(f"git checkout d98a0fa8dc715308dfc73eac5c553b69c6ee072b")
+ run_cmd(f"git checkout d73267a415fb0eba98477afa43ef71ffd82a7157")
+ run_cmd(f"git checkout 1a03d01356ae79179e1fb8d8915dc9cc79925742")
+ run_cmd(f"git checkout 81497bb3115e92c754300c9b3992df428886a3e9")
+ run_cmd(f"git checkout c5af1f8edcf79cb70f065c0110e279e78e48caf9")
+ run_cmd(f"git checkout cdb3c90109387fa4dfa92f53c3864c71170ffc77")
+
+ # Edit the file here, before copying
+ #edit_file(f"{temp_repo_path}/infer-web.py")
+
+ # Copy all files from the cloned repository to the existing path
+ copy_all_files_in_directory(temp_repo_path, repo_path)
+ print(f"Copying all {new_folder_name} files from GitHub.")
+
+ # Change working directory back to /content/
+ os.chdir('/content/')
+ print("Changed path back to /content/")
+
+ # Remove the temporary cloned repository
+ shutil.rmtree(temp_repo_path)
+
+ # Call the function
+ clone_and_copy_repo(repo_path)
+
+ # Download the credentials file for RVC archive sheet
+ os.makedirs('/content/Applio-RVC-Fork/stats/', exist_ok=True)
+ run_cmd("wget -q https://cdn.discordapp.com/attachments/945486970883285045/1114717554481569802/peppy-generator-388800-07722f17a188.json -O /content/Applio-RVC-Fork/stats/peppy-generator-388800-07722f17a188.json")
+
+ # Forcefully delete any existing torchcrepe dependencies downloaded from an earlier run just in case
+ shutil.rmtree('/content/Applio-RVC-Fork/torchcrepe', ignore_errors=True)
+ shutil.rmtree('/content/torchcrepe', ignore_errors=True)
+
+ # Download the torchcrepe folder from the maxrmorrison/torchcrepe repository
+ run_cmd("git clone https://github.com/maxrmorrison/torchcrepe.git")
+ shutil.move('/content/torchcrepe/torchcrepe', '/content/Applio-RVC-Fork/')
+ shutil.rmtree('/content/torchcrepe', ignore_errors=True) # Delete the torchcrepe repository folder
+
+ # Change the current directory to /content/Applio-RVC-Fork
+ os.chdir('/content/Applio-RVC-Fork')
+ os.makedirs('pretrained', exist_ok=True)
+ os.makedirs('uvr5_weights', exist_ok=True)
+
+def download_file(url, filepath):
+ response = requests.get(url, stream=True)
+ response.raise_for_status()
+
+ with open(filepath, "wb") as file:
+ for chunk in response.iter_content(chunk_size=8192):
+ if chunk:
+ file.write(chunk)
+
+def download_pretrained_models():
+ pretrained_models = {
+ "pretrained": [
+ "D40k.pth",
+ "G40k.pth",
+ "f0D40k.pth",
+ "f0G40k.pth"
+ ],
+ "pretrained_v2": [
+ "D40k.pth",
+ "G40k.pth",
+ "f0D40k.pth",
+ "f0G40k.pth",
+ "f0G48k.pth",
+ "f0D48k.pth"
+ ],
+ "uvr5_weights": [
+ "HP2-人声vocals+非人声instrumentals.pth",
+ "HP5-主旋律人声vocals+其他instrumentals.pth",
+ "VR-DeEchoNormal.pth",
+ "VR-DeEchoDeReverb.pth",
+ "VR-DeEchoAggressive.pth",
+ "HP5_only_main_vocal.pth",
+ "HP3_all_vocals.pth",
+ "HP2_all_vocals.pth"
+ ]
+ }
+ part2 = "I"
+ base_url = "https://huggingface.co/lj1995/VoiceConversionWebU" + part2 + "/resolve/main/"
+ base_path = "/content/Applio-RVC-Fork/"
+ base_pathm = base_path
+
+ # Calculate total number of files to download
+ total_files = sum(len(files) for files in pretrained_models.values()) + 1 # +1 for hubert_base.pt
+
+ with tqdm(total=total_files, desc="Downloading files") as pbar:
+ for folder, models in pretrained_models.items():
+ folder_path = os.path.join(base_path, folder)
+ os.makedirs(folder_path, exist_ok=True)
+ for model in models:
+ url = base_url + folder + "/" + model
+ filepath = os.path.join(folder_path, model)
+ download_file(url, filepath)
+ pbar.update()
+
+ # Download hubert_base.pt to the base path
+ hubert_url = base_url + "hubert_base.pt"
+ hubert_filepath = os.path.join(base_pathm, "hubert_base.pt")
+ download_file(hubert_url, hubert_filepath)
+ pbar.update()
+def clone_repository(run_download):
+ with ThreadPoolExecutor(max_workers=2) as executor:
+ executor.submit(run_script)
+ if run_download:
+ executor.submit(download_pretrained_models)
diff --git a/Applio-RVC-Fork/utils/dependency.py b/Applio-RVC-Fork/utils/dependency.py
new file mode 100644
index 0000000000000000000000000000000000000000..b70338b02d31b1ef455fbac817d418d328db518d
--- /dev/null
+++ b/Applio-RVC-Fork/utils/dependency.py
@@ -0,0 +1,170 @@
+import os
+import csv
+import shutil
+import tarfile
+import subprocess
+from pathlib import Path
+from datetime import datetime
+
+def install_packages_but_jank_af():
+ packages = ['build-essential', 'python3-dev', 'ffmpeg', 'aria2']
+ pip_packages = ['pip', 'setuptools', 'wheel', 'httpx==0.23.0', 'faiss-gpu', 'fairseq', 'gradio==3.34.0',
+ 'ffmpeg', 'ffmpeg-python', 'praat-parselmouth', 'pyworld', 'numpy==1.23.5',
+ 'numba==0.56.4', 'librosa==0.9.2', 'mega.py', 'gdown', 'onnxruntime', 'pyngrok==4.1.12',
+ 'gTTS', 'elevenlabs', 'wget', 'tensorboardX', 'unidecode', 'huggingface-hub', 'stftpitchshift==1.5.1',
+ 'yt-dlp', 'pedalboard', 'pathvalidate', 'nltk', 'edge-tts', 'git+https://github.com/suno-ai/bark.git', 'python-dotenv' , 'av']
+
+ print("Updating and installing system packages...")
+ for package in packages:
+ print(f"Installing {package}...")
+ subprocess.check_call(['apt-get', 'install', '-qq', '-y', package])
+
+ print("Updating and installing pip packages...")
+ subprocess.check_call(['pip', 'install', '--upgrade'] + pip_packages)
+
+ print('Packages up to date.')
+
+
+def setup_environment(ForceUpdateDependencies, ForceTemporaryStorage):
+ # Mounting Google Drive
+ if not ForceTemporaryStorage:
+ from google.colab import drive
+
+ if not os.path.exists('/content/drive'):
+ drive.mount('/content/drive')
+ else:
+ print('Drive is already mounted. Proceeding...')
+
+ # Function to install dependencies with progress
+ def install_packages():
+ packages = ['build-essential', 'python3-dev', 'ffmpeg', 'aria2']
+ pip_packages = ['pip', 'setuptools', 'wheel', 'httpx==0.23.0', 'faiss-gpu', 'fairseq', 'gradio==3.34.0',
+ 'ffmpeg', 'ffmpeg-python', 'praat-parselmouth', 'pyworld', 'numpy==1.23.5',
+ 'numba==0.56.4', 'librosa==0.9.2', 'mega.py', 'gdown', 'onnxruntime', 'pyngrok==4.1.12',
+ 'gTTS', 'elevenlabs', 'wget', 'tensorboardX', 'unidecode', 'huggingface-hub', 'stftpitchshift==1.5.1',
+ 'yt-dlp', 'pedalboard', 'pathvalidate', 'nltk', 'edge-tts', 'git+https://github.com/suno-ai/bark.git', 'python-dotenv' , 'av']
+
+ print("Updating and installing system packages...")
+ for package in packages:
+ print(f"Installing {package}...")
+ subprocess.check_call(['apt-get', 'install', '-qq', '-y', package])
+
+ print("Updating and installing pip packages...")
+ subprocess.check_call(['pip', 'install', '--upgrade'] + pip_packages)
+
+
+ print('Packages up to date.')
+
+ # Function to scan a directory and writes filenames and timestamps
+ def scan_and_write(base_path, output_file):
+ with open(output_file, 'w', newline='') as f:
+ writer = csv.writer(f)
+ for dirpath, dirs, files in os.walk(base_path):
+ for filename in files:
+ fname = os.path.join(dirpath, filename)
+ try:
+ mtime = os.path.getmtime(fname)
+ writer.writerow([fname, mtime])
+ except Exception as e:
+ print(f'Skipping irrelevant nonexistent file {fname}: {str(e)}')
+ print(f'Finished recording filesystem timestamps to {output_file}.')
+
+ # Function to compare files
+ def compare_files(old_file, new_file):
+ old_files = {}
+ new_files = {}
+
+ with open(old_file, 'r') as f:
+ reader = csv.reader(f)
+ old_files = {rows[0]:rows[1] for rows in reader}
+
+ with open(new_file, 'r') as f:
+ reader = csv.reader(f)
+ new_files = {rows[0]:rows[1] for rows in reader}
+
+ removed_files = old_files.keys() - new_files.keys()
+ added_files = new_files.keys() - old_files.keys()
+ unchanged_files = old_files.keys() & new_files.keys()
+
+ changed_files = {f for f in unchanged_files if old_files[f] != new_files[f]}
+
+ for file in removed_files:
+ print(f'File has been removed: {file}')
+
+ for file in changed_files:
+ print(f'File has been updated: {file}')
+
+ return list(added_files) + list(changed_files)
+
+ # Check if CachedRVC.tar.gz exists
+ if ForceTemporaryStorage:
+ file_path = '/content/CachedRVC.tar.gz'
+ else:
+ file_path = '/content/drive/MyDrive/RVC_Cached/CachedRVC.tar.gz'
+
+ content_file_path = '/content/CachedRVC.tar.gz'
+ extract_path = '/'
+
+ if not os.path.exists(file_path):
+ folder_path = os.path.dirname(file_path)
+ os.makedirs(folder_path, exist_ok=True)
+ print('No cached dependency install found. Attempting to download GitHub backup..')
+
+ try:
+ download_url = "https://github.com/kalomaze/QuickMangioFixes/releases/download/release3/CachedRVC.tar.gz"
+ subprocess.run(["wget", "-O", file_path, download_url])
+ print('Download completed successfully!')
+ except Exception as e:
+ print('Download failed:', str(e))
+
+ # Delete the failed download file
+ if os.path.exists(file_path):
+ os.remove(file_path)
+ print('Failed download file deleted. Continuing manual backup..')
+
+ if Path(file_path).exists():
+ if ForceTemporaryStorage:
+ print('Finished downloading CachedRVC.tar.gz.')
+ else:
+ print('CachedRVC.tar.gz found on Google Drive. Proceeding to copy and extract...')
+
+ # Check if ForceTemporaryStorage is True and skip copying if it is
+ if ForceTemporaryStorage:
+ pass
+ else:
+ shutil.copy(file_path, content_file_path)
+
+ print('Beginning backup copy operation...')
+
+ with tarfile.open(content_file_path, 'r:gz') as tar:
+ for member in tar.getmembers():
+ target_path = os.path.join(extract_path, member.name)
+ try:
+ tar.extract(member, extract_path)
+ except Exception as e:
+ print('Failed to extract a file (this isn\'t normal)... forcing an update to compensate')
+ ForceUpdateDependencies = True
+ print(f'Extraction of {content_file_path} to {extract_path} completed.')
+
+ if ForceUpdateDependencies:
+ install_packages()
+ ForceUpdateDependencies = False
+ else:
+ print('CachedRVC.tar.gz not found. Proceeding to create an index of all current files...')
+ scan_and_write('/usr/', '/content/usr_files.csv')
+
+ install_packages()
+
+ scan_and_write('/usr/', '/content/usr_files_new.csv')
+ changed_files = compare_files('/content/usr_files.csv', '/content/usr_files_new.csv')
+
+ with tarfile.open('/content/CachedRVC.tar.gz', 'w:gz') as new_tar:
+ for file in changed_files:
+ new_tar.add(file)
+ print(f'Added to tar: {file}')
+
+ os.makedirs('/content/drive/MyDrive/RVC_Cached', exist_ok=True)
+ shutil.copy('/content/CachedRVC.tar.gz', '/content/drive/MyDrive/RVC_Cached/CachedRVC.tar.gz')
+ print('Updated CachedRVC.tar.gz copied to Google Drive.')
+ print('Dependencies fully up to date; future runs should be faster.')
+
diff --git a/Applio-RVC-Fork/utils/i18n.py b/Applio-RVC-Fork/utils/i18n.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e75d2bc26ff86ab1716b8d7f239ad9f5cc1e32d
--- /dev/null
+++ b/Applio-RVC-Fork/utils/i18n.py
@@ -0,0 +1,28 @@
+import locale
+import json
+import os
+
+
+def load_language_list(language):
+ with open(f"./i18n/{language}.json", "r", encoding="utf-8") as f:
+ language_list = json.load(f)
+ return language_list
+
+
+class I18nAuto:
+ def __init__(self, language=None):
+ if language in ["Auto", None]:
+ language = "es_ES"
+ if not os.path.exists(f"./i18n/{language}.json"):
+ language = "es_ES"
+ language = "es_ES"
+ self.language = language
+ # print("Use Language:", language)
+ self.language_map = load_language_list(language)
+
+ def __call__(self, key):
+ return self.language_map.get(key, key)
+
+ def print(self):
+ # print("Use Language:", self.language)
+ print("")
diff --git a/Applio_(Mangio_RVC_Fork).ipynb b/Applio_(Mangio_RVC_Fork).ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..4440de685158080ff706ee4099b0b7188851ceda
--- /dev/null
+++ b/Applio_(Mangio_RVC_Fork).ipynb
@@ -0,0 +1,169 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "id": "izLwNF_8T1TK"
+ },
+ "outputs": [],
+ "source": [
+ "#@title **🍏 Applio (Mangio-RVC-Fork)**\n",
+ "import time\n",
+ "import os\n",
+ "import subprocess\n",
+ "import shutil\n",
+ "import threading\n",
+ "import base64\n",
+ "import threading\n",
+ "import time\n",
+ "from IPython.display import HTML, clear_output\n",
+ "\n",
+ "nosv_name1 = base64.b64decode(('ZXh0ZXJuYWxj').encode('ascii')).decode('ascii')\n",
+ "nosv_name2 = base64.b64decode(('b2xhYmNvZGU=').encode('ascii')).decode('ascii')\n",
+ "guebui = base64.b64decode(('V2U=').encode('ascii')).decode('ascii')\n",
+ "guebui2 = base64.b64decode(('YlVJ').encode('ascii')).decode('ascii')\n",
+ "pbestm = base64.b64decode(('cm12cGU=').encode('ascii')).decode('ascii')\n",
+ "tryre = base64.b64decode(('UmV0cmlldmFs').encode('ascii')).decode('ascii')\n",
+ "\n",
+ "xdsame = '/content/'+ tryre +'-based-Voice-Conversion-' + guebui + guebui2 +'/'\n",
+ "\n",
+ "collapsible_section = \"\"\"\n",
+ "
\n",
+ "
\n",
+ "\n",
+ " 🚀 Click to learn more about Applio
\n",
+ " \n",
+ "
\n",
+ " - Mangio-RVC-Fork - Source of inspiration and base for this improved code, special thanks to the developers.
\n",
+ " - UltimateVocalRemover - Used for voice and instrument separation.
\n",
+ " - Vidal, Blaise & Aitron - Contributors to the Applio version.
\n",
+ " - kalomaze - Creator of external scripts that help the functioning of Applio.
\n",
+ "
\n",
+ "
Join and contribute to the project on our GitHub repository.
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ "
\n",
+ "\"\"\"\n",
+ "#@markdown **Settings:**\n",
+ "ForceUpdateDependencies = True\n",
+ "ForceNoMountDrive = False\n",
+ "#@markdown Restore your backup from Google Drive.\n",
+ "LoadBackupDrive = False #@param{type:\"boolean\"}\n",
+ "#@markdown Make regular backups of your model's training.\n",
+ "AutoBackups = True #@param{type:\"boolean\"}\n",
+ "if not os.path.exists(xdsame):\n",
+ " current_path = os.getcwd()\n",
+ " shutil.rmtree('/content/')\n",
+ " os.makedirs('/content/', exist_ok=True)\n",
+ "\n",
+ " os.chdir(current_path)\n",
+ " !git clone https://github.com/IAHispano/$nosv_name1$nosv_name2 /content/$tryre-based-Voice-Conversion-$guebui$guebui2/utils\n",
+ " clear_output()\n",
+ "\n",
+ " os.chdir(xdsame)\n",
+ " from utils.dependency import *\n",
+ " from utils.clonerepo_experimental import *\n",
+ " os.chdir(\"..\")\n",
+ "\n",
+ "\n",
+ "\n",
+ " setup_environment(ForceUpdateDependencies, ForceNoMountDrive)\n",
+ " clone_repository(True)\n",
+ "\n",
+ " !wget https://huggingface.co/lj1995/VoiceConversion$guebui$guebui2/resolve/main/rmvpe.pt -P /content/Retrieval-based-Voice-Conversion-$guebui$guebui2/\n",
+ " clear_output()\n",
+ "\n",
+ "base_path = \"/content/Retrieval-based-Voice-Conversion-$guebui$guebui2/\"\n",
+ "clear_output()\n",
+ "\n",
+ "\n",
+ "\n",
+ "from utils import backups\n",
+ "\n",
+ "LOGS_FOLDER = xdsame + '/logs'\n",
+ "if not os.path.exists(LOGS_FOLDER):\n",
+ " os.makedirs(LOGS_FOLDER)\n",
+ " clear_output()\n",
+ "\n",
+ "WEIGHTS_FOLDER = xdsame + '/logs' + '/weights'\n",
+ "if not os.path.exists(WEIGHTS_FOLDER):\n",
+ " os.makedirs(WEIGHTS_FOLDER)\n",
+ " clear_output()\n",
+ "\n",
+ "others_FOLDER = xdsame + '/audio-others'\n",
+ "if not os.path.exists(others_FOLDER):\n",
+ " os.makedirs(others_FOLDER)\n",
+ " clear_output()\n",
+ "\n",
+ "audio_outputs_FOLDER = xdsame + '/audio-outputs'\n",
+ "if not os.path.exists(audio_outputs_FOLDER):\n",
+ " os.makedirs(audio_outputs_FOLDER)\n",
+ " clear_output()\n",
+ "\n",
+ "if LoadBackupDrive:\n",
+ " backups.import_google_drive_backup()\n",
+ " clear_output()\n",
+ "\n",
+ "#@markdown Choose the language in which you want the interface to be available.\n",
+ "i18n_path = xdsame + 'i18n.py'\n",
+ "i18n_new_path = xdsame + 'utils/i18n.py'\n",
+ "try:\n",
+ " if os.path.exists(i18n_path) and os.path.exists(i18n_new_path):\n",
+ " shutil.move(i18n_new_path, i18n_path)\n",
+ "\n",
+ " SelectedLanguage = \"en_US\" #@param [\"es_ES\", \"en_US\", \"zh_CN\", \"ar_AR\", \"id_ID\", \"pt_PT\", \"ru_RU\", \"ur_UR\", \"tr_TR\", \"it_IT\", \"de_DE\"]\n",
+ " new_language_line = ' language = \"' + SelectedLanguage + '\"\\n'\n",
+ "#@markdown If you need more help, feel free to join our official Discord server!\n",
+ " with open(i18n_path, 'r') as file:\n",
+ " lines = file.readlines()\n",
+ "\n",
+ " with open(i18n_path, 'w') as file:\n",
+ " for index, line in enumerate(lines):\n",
+ " if index == 14:\n",
+ " file.write(new_language_line)\n",
+ " else:\n",
+ " file.write(line)\n",
+ "\n",
+ "except FileNotFoundError:\n",
+ " print(\"Translation couldn't be applied successfully. Please restart the environment and run the cell again.\")\n",
+ "\n",
+ "def start_web_server():\n",
+ " %cd /content/$tryre-based-Voice-Conversion-$guebui$guebui2\n",
+ " %load_ext tensorboard\n",
+ " clear_output()\n",
+ " %tensorboard --logdir /content/$tryre-based-Voice-Conversion-$guebui$guebui2/logs\n",
+ " !mkdir -p /content/$tryre-based-Voice-Conversion-$guebui$guebui2/audios\n",
+ " display(HTML(collapsible_section))\n",
+ " !python3 infer-web.py --colab --pycmd python3\n",
+ "\n",
+ "if AutoBackups:\n",
+ " web_server_thread = threading.Thread(target=start_web_server)\n",
+ " web_server_thread.start()\n",
+ " backups.backup_files()\n",
+ "\n",
+ "else:\n",
+ " start_web_server()"
+ ]
+ }
+ ],
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..b81f131c79cc585012b28002f4916491e85f3a33
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+# syntax=docker/dockerfile:1
+
+FROM python:3.10-bullseye
+
+EXPOSE 7865
+
+WORKDIR /app
+
+COPY . .
+
+RUN apt update && apt install -y -qq ffmpeg aria2 && apt clean
+
+RUN pip3 install --no-cache-dir -r requirements.txt
+
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/D40k.pth -d assets/pretrained_v2/ -o D40k.pth
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/G40k.pth -d assets/pretrained_v2/ -o G40k.pth
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0D40k.pth -d assets/pretrained_v2/ -o f0D40k.pth
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0G40k.pth -d assets/pretrained_v2/ -o f0G40k.pth
+
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP2-人声vocals+非人声instrumentals.pth -d assets/uvr5_weights/ -o HP2-人声vocals+非人声instrumentals.pth
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP5-主旋律人声vocals+其他instrumentals.pth -d assets/uvr5_weights/ -o HP5-主旋律人声vocals+其他instrumentals.pth
+
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/hubert_base.pt -d assets/hubert -o hubert_base.pt
+
+RUN aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/rmvpe.pt -d assets/hubert -o rmvpe.pt
+
+VOLUME [ "/app/weights", "/app/opt" ]
+
+CMD ["python3", "infer-web.py"]
\ No newline at end of file
diff --git a/Fixes/local_fixes.py b/Fixes/local_fixes.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a418076eee6f65fe06eb0f607061796b839c1ee
--- /dev/null
+++ b/Fixes/local_fixes.py
@@ -0,0 +1,136 @@
+import os
+import sys
+import time
+import shutil
+import requests
+import zipfile
+
+def insert_new_line(file_name, line_to_find, text_to_insert):
+ lines = []
+ with open(file_name, 'r', encoding='utf-8') as read_obj:
+ lines = read_obj.readlines()
+ already_exists = False
+ with open(file_name + '.tmp', 'w', encoding='utf-8') as write_obj:
+ for i in range(len(lines)):
+ write_obj.write(lines[i])
+ if lines[i].strip() == line_to_find:
+ # If next line exists and starts with sys.path.append, skip
+ if i+1 < len(lines) and lines[i+1].strip().startswith("sys.path.append"):
+ print('It was already fixed! Skip adding a line...')
+ already_exists = True
+ break
+ else:
+ write_obj.write(text_to_insert + '\n')
+ # If no existing sys.path.append line was found, replace the original file
+ if not already_exists:
+ os.replace(file_name + '.tmp', file_name)
+ return True
+ else:
+ # If existing line was found, delete temporary file
+ os.remove(file_name + '.tmp')
+ return False
+
+def replace_in_file(file_name, old_text, new_text):
+ with open(file_name, 'r', encoding='utf-8') as file:
+ file_contents = file.read()
+
+ if old_text in file_contents:
+ file_contents = file_contents.replace(old_text, new_text)
+ with open(file_name, 'w', encoding='utf-8') as file:
+ file.write(file_contents)
+ return True
+
+ return False
+
+if __name__ == "__main__":
+ current_path = os.getcwd()
+ file_name = os.path.join(current_path, "infer", "modules", "train", "extract", "extract_f0_print.py")
+ line_to_find = 'import numpy as np, logging'
+ text_to_insert = "sys.path.append(r'" + current_path + "')"
+
+
+ success_1 = insert_new_line(file_name, line_to_find, text_to_insert)
+ if success_1:
+ print('The first operation was successful!')
+ else:
+ print('He skipped the first operation because it was already fixed!')
+
+ file_name = 'infer-web.py'
+ old_text = 'with gr.Blocks(theme=gr.themes.Soft()) as app:'
+ new_text = 'with gr.Blocks() as app:'
+
+ success_2 = replace_in_file(file_name, old_text, new_text)
+ if success_2:
+ print('The second operation was successful!')
+ else:
+ print('The second operation was omitted because it was already fixed!')
+
+ print('Local corrections successful! You should now be able to infer and train locally in Applio RVC Fork.')
+
+ time.sleep(5)
+
+def find_torchcrepe_directory(directory):
+ """
+ Recursively searches for the topmost folder named 'torchcrepe' within a directory.
+ Returns the path of the directory found or None if none is found.
+ """
+ for root, dirs, files in os.walk(directory):
+ if 'torchcrepe' in dirs:
+ return os.path.join(root, 'torchcrepe')
+ return None
+
+def download_and_extract_torchcrepe():
+ url = 'https://github.com/maxrmorrison/torchcrepe/archive/refs/heads/master.zip'
+ temp_dir = 'temp_torchcrepe'
+ destination_dir = os.getcwd()
+
+ try:
+ torchcrepe_dir_path = os.path.join(destination_dir, 'torchcrepe')
+
+ if os.path.exists(torchcrepe_dir_path):
+ print("Skipping the torchcrepe download. The folder already exists.")
+ return
+
+ # Download the file
+ print("Starting torchcrepe download...")
+ response = requests.get(url)
+
+ # Raise an error if the GET request was unsuccessful
+ response.raise_for_status()
+ print("Download completed.")
+
+ # Save the downloaded file
+ zip_file_path = os.path.join(temp_dir, 'master.zip')
+ os.makedirs(temp_dir, exist_ok=True)
+ with open(zip_file_path, 'wb') as file:
+ file.write(response.content)
+ print(f"Zip file saved to {zip_file_path}")
+
+ # Extract the zip file
+ print("Extracting content...")
+ with zipfile.ZipFile(zip_file_path, 'r') as zip_file:
+ zip_file.extractall(temp_dir)
+ print("Extraction completed.")
+
+ # Locate the torchcrepe folder and move it to the destination directory
+ torchcrepe_dir = find_torchcrepe_directory(temp_dir)
+ if torchcrepe_dir:
+ shutil.move(torchcrepe_dir, destination_dir)
+ print(f"Moved the torchcrepe directory to {destination_dir}!")
+ else:
+ print("The torchcrepe directory could not be located.")
+
+ except Exception as e:
+ print("Torchcrepe not successfully downloaded", e)
+
+ # Clean up temporary directory
+ if os.path.exists(temp_dir):
+ shutil.rmtree(temp_dir)
+
+# Run the function
+download_and_extract_torchcrepe()
+
+temp_dir = 'temp_torchcrepe'
+
+if os.path.exists(temp_dir):
+ shutil.rmtree(temp_dir)
diff --git a/Fixes/tensor-launch.py b/Fixes/tensor-launch.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd4ec997fb4b1338d7f29912987865899281b083
--- /dev/null
+++ b/Fixes/tensor-launch.py
@@ -0,0 +1,15 @@
+import threading
+import time
+from tensorboard import program
+import os
+
+log_path = "logs"
+
+if __name__ == "__main__":
+ tb = program.TensorBoard()
+ tb.configure(argv=[None, '--logdir', log_path])
+ url = tb.launch()
+ print(f'Tensorboard can be accessed at: {url}')
+
+ while True:
+ time.sleep(600) # Keep the main thread running
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..c441961e22604dff6d32160a20972819efdd3872
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,59 @@
+MIT License
+
+Copyright (c) 2023 liujing04
+Copyright (c) 2023 源文雨
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+The licenses for related libraries are as follows:
+
+ContentVec
+https://github.com/auspicious3000/contentvec/blob/main/LICENSE
+MIT License
+
+VITS
+https://github.com/jaywalnut310/vits/blob/main/LICENSE
+MIT License
+
+HIFIGAN
+https://github.com/jik876/hifi-gan/blob/master/LICENSE
+MIT License
+
+gradio
+https://github.com/gradio-app/gradio/blob/main/LICENSE
+Apache License 2.0
+
+ffmpeg
+https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
+https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2021-02-28-12-32/ffmpeg-n4.3.2-160-gfbb9368226-win64-lgpl-4.3.zip
+LPGLv3 License
+MIT License
+
+ultimatevocalremovergui
+https://github.com/Anjok07/ultimatevocalremovergui/blob/master/LICENSE
+https://github.com/yang123qwe/vocal_separation_by_uvr5
+MIT License
+
+audio-slicer
+https://github.com/openvpi/audio-slicer/blob/main/LICENSE
+MIT License
+
+PySimpleGUI
+https://github.com/PySimpleGUI/PySimpleGUI/blob/master/license.txt
+LPGLv3 License
\ No newline at end of file
diff --git a/LazyImport.py b/LazyImport.py
new file mode 100644
index 0000000000000000000000000000000000000000..5bdb05ddd5a546a43adba7274b4c3465bb77f2f5
--- /dev/null
+++ b/LazyImport.py
@@ -0,0 +1,13 @@
+from importlib.util import find_spec, LazyLoader, module_from_spec
+from sys import modules
+
+def lazyload(name):
+ if name in modules:
+ return modules[name]
+ else:
+ spec = find_spec(name)
+ loader = LazyLoader(spec.loader)
+ module = module_from_spec(spec)
+ modules[name] = module
+ loader.exec_module(module)
+ return module
\ No newline at end of file
diff --git a/MDX-Net_Colab.ipynb b/MDX-Net_Colab.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..a884caf08a9f7b9747346f5def57d24b7258f5b8
--- /dev/null
+++ b/MDX-Net_Colab.ipynb
@@ -0,0 +1,524 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "wX9xzLur4tus"
+ },
+ "source": [
+ "# MDX-Net Colab\n",
+ "
\n",
+ "
\n",
+ "
Trained models provided in this notebook are from UVR-GUI.\n",
+ "
\n",
+ "\n",
+ "
\n",
+ "
OFFICIAL UVR GITHUB PAGE: here.\n",
+ "
\n",
+ "\n",
+ "
\n",
+ "
OFFICIAL CLI Version: here.\n",
+ "
\n",
+ "\n",
+ "
Ultimate Vocal Remover (unofficial)\n",
+ "
MDX-Net by kuielab and adapted for Colaboratory by AudioHacker.\n",
+ "\n",
+ "
Your support means a lot to me. If you enjoy my work, please consider buying me a ko-fi:
\n",
+ "[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/X8X6M8FR0)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "3J69RV7G8ocb",
+ "cellView": "form"
+ },
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "import os\n",
+ "import os.path\n",
+ "import gc\n",
+ "import psutil\n",
+ "import requests\n",
+ "import subprocess\n",
+ "import glob\n",
+ "import time\n",
+ "import logging\n",
+ "import sys\n",
+ "from bs4 import BeautifulSoup\n",
+ "from google.colab import drive, files, output\n",
+ "from IPython.display import Audio, display\n",
+ "\n",
+ "if \"first_cell_ran\" in locals():\n",
+ " print(\"You've ran this cell for this session. No need to run it again.\\nif you think something went wrong or you want to change mounting path, restart the runtime.\")\n",
+ "else:\n",
+ " print('Setting up... please wait around 1-2 minute(s).')\n",
+ "\n",
+ " branch = \"https://github.com/NaJeongMo/Colab-for-MDX_B\"\n",
+ "\n",
+ " model_params = \"https://raw.githubusercontent.com/TRvlvr/application_data/main/mdx_model_data/model_data.json\"\n",
+ " _Models = \"https://github.com/TRvlvr/model_repo/releases/download/all_public_uvr_models/\"\n",
+ " # _models = \"https://pastebin.com/raw/jBzYB8vz\"\n",
+ " _models = \"https://raw.githubusercontent.com/TRvlvr/application_data/main/filelists/download_checks.json\"\n",
+ " stem_naming = \"https://pastebin.com/raw/mpH4hRcF\"\n",
+ " arl_check_endpoint = 'https://dz.doubledouble.top/check' # param: arl?=<>\n",
+ "\n",
+ " file_folder = \"Colab-for-MDX_B\"\n",
+ "\n",
+ " model_ids = requests.get(_models).json()\n",
+ " model_ids = model_ids[\"mdx_download_list\"].values()\n",
+ "\n",
+ " model_params = requests.get(model_params).json()\n",
+ " stem_naming = requests.get(stem_naming).json()\n",
+ "\n",
+ " os.makedirs(\"tmp_models\", exist_ok=True)\n",
+ "\n",
+ " # @markdown If you don't wish to mount google drive, uncheck this box.\n",
+ " MountDrive = True # @param{type:\"boolean\"}\n",
+ " # @markdown The path for the drive to be mounted: Please be cautious when modifying this as it can cause issues if not done properly.\n",
+ " mounting_path = \"/content/drive/MyDrive\" # @param [\"snippets:\",\"/content/drive/MyDrive\",\"/content/drive/Shareddrives/\", \"/content/drive/Shareddrives/Shared Drive\"]{allow-input: true}\n",
+ " # @markdown Force update and disregard local changes: discards all local modifications in your repository, effectively replacing all files with the versions from the original commit.\n",
+ " force_update = False # @param{type:\"boolean\"}\n",
+ " # @markdown Auto Update (does not discard your changes)\n",
+ " auto_update = True # @param{type:\"boolean\"}\n",
+ "\n",
+ "\n",
+ " reqs_apt = [] # !sudo apt-get install\n",
+ " reqs_pip = [\"librosa>=0.6.3,<0.9\", \"onnxruntime_gpu\", \"deemix\", \"yt_dlp\"] # pip3 install\n",
+ "\n",
+ " class hide_opt: # hide outputs\n",
+ " def __enter__(self):\n",
+ " self._original_stdout = sys.stdout\n",
+ " sys.stdout = open(os.devnull, \"w\")\n",
+ "\n",
+ " def __exit__(self, exc_type, exc_val, exc_tb):\n",
+ " sys.stdout.close()\n",
+ " sys.stdout = self._original_stdout\n",
+ "\n",
+ " def get_size(bytes, suffix=\"B\"): # read ram\n",
+ " global svmem\n",
+ " factor = 1024\n",
+ " for unit in [\"\", \"K\", \"M\", \"G\", \"T\", \"P\"]:\n",
+ " if bytes < factor:\n",
+ " return f\"{bytes:.2f}{unit}{suffix}\"\n",
+ " bytes /= factor\n",
+ " svmem = psutil.virtual_memory()\n",
+ "\n",
+ "\n",
+ " print('installing requirements...',end=' ')\n",
+ " with hide_opt():\n",
+ " for x in reqs_apt:\n",
+ " subprocess.run([\"sudo\", \"apt-get\", \"install\", x])\n",
+ " for x in reqs_pip:\n",
+ " subprocess.run([\"python3\", \"-m\", \"pip\", \"install\", x])\n",
+ " print('done')\n",
+ "\n",
+ " def install_or_mount_drive():\n",
+ " print(\n",
+ " \"Please log in to your account by following the prompts in the pop-up tab.\\nThis step is necessary to install the files to your Google Drive.\\nIf you have any concerns about the safety of this notebook, you can choose not to mount your drive by unchecking the \\\"MountDrive\\\" checkbox.\"\n",
+ " )\n",
+ " drive.mount(\"/content/drive\", force_remount=True)\n",
+ " os.chdir(mounting_path)\n",
+ " # check if previous installation is done\n",
+ " if os.path.exists(os.path.join(mounting_path, file_folder)):\n",
+ " # update checking\n",
+ " os.chdir(file_folder)\n",
+ "\n",
+ " if force_update:\n",
+ " print('Force updating...')\n",
+ "\n",
+ " commands = [\n",
+ " [\"git\", \"pull\"],\n",
+ " [\"git\", \"checkout\", \"--\", \".\"],\n",
+ " ]\n",
+ "\n",
+ " for cmd in commands:\n",
+ " subprocess.run(cmd)\n",
+ "\n",
+ " elif auto_update:\n",
+ " print('Checking for updates...')\n",
+ " commands = [\n",
+ " [\"git\", \"pull\"],\n",
+ " ]\n",
+ "\n",
+ " for cmd in commands:\n",
+ " subprocess.run(cmd)\n",
+ " else:\n",
+ " subprocess.run([\"git\", \"clone\", \"https://github.com/NaJeongMo/Colab-for-MDX_B.git\"])\n",
+ " os.chdir(file_folder)\n",
+ "\n",
+ " def use_uvr_without_saving():\n",
+ " global mounting_path\n",
+ " print(\"Notice: files won't be saved to personal drive.\")\n",
+ " print(f\"Downloading {file_folder}...\", end=\" \")\n",
+ " mounting_path = \"/content\"\n",
+ " with hide_opt():\n",
+ " os.chdir(mounting_path)\n",
+ " subprocess.run([\"git\", \"clone\", \"https://github.com/NaJeongMo/Colab-for-MDX_B.git\"])\n",
+ " os.chdir(file_folder)\n",
+ "\n",
+ " if MountDrive:\n",
+ " install_or_mount_drive()\n",
+ " else:\n",
+ " use_uvr_without_saving()\n",
+ " print(\"done!\")\n",
+ " if not os.path.exists(\"tracks\"):\n",
+ " os.mkdir(\"tracks\")\n",
+ "\n",
+ " print('Importing required libraries...',end=' ')\n",
+ "\n",
+ " import os\n",
+ " import mdx\n",
+ " import librosa\n",
+ " import torch\n",
+ " import soundfile as sf\n",
+ " import numpy as np\n",
+ " import yt_dlp\n",
+ "\n",
+ " from deezer import Deezer\n",
+ " from deezer import TrackFormats\n",
+ " import deemix\n",
+ " from deemix.settings import load as loadSettings\n",
+ " from deemix.downloader import Downloader\n",
+ " from deemix import generateDownloadObject\n",
+ "\n",
+ " logger = logging.getLogger(\"yt_dlp\")\n",
+ " logger.setLevel(logging.ERROR)\n",
+ "\n",
+ " def id_to_ptm(mkey):\n",
+ " if mkey in model_ids:\n",
+ " mpath = f\"/content/tmp_models/{mkey}\"\n",
+ " if not os.path.exists(f'/content/tmp_models/{mkey}'):\n",
+ " print('Downloading model...',end=' ')\n",
+ " subprocess.run(\n",
+ " [\"wget\", _Models+mkey, \"-O\", mpath]\n",
+ " )\n",
+ " print(f'saved to {mpath}')\n",
+ " # get_ipython().system(f'gdown {model_id} -O /content/tmp_models/{mkey}')\n",
+ " return mpath\n",
+ " else:\n",
+ " return mpath\n",
+ " else:\n",
+ " mpath = f'models/{mkey}'\n",
+ " return mpath\n",
+ "\n",
+ " def prepare_mdx(custom_param=False, dim_f=None, dim_t=None, n_fft=None, stem_name=None, compensation=None):\n",
+ " device = torch.device('cuda:0') if torch.cuda.is_available() else torch.device('cpu')\n",
+ " if custom_param:\n",
+ " assert not (dim_f is None or dim_t is None or n_fft is None or compensation is None), 'Custom parameter selected, but incomplete parameters are provided.'\n",
+ " mdx_model = mdx.MDX_Model(\n",
+ " device,\n",
+ " dim_f = dim_f,\n",
+ " dim_t = dim_t,\n",
+ " n_fft = n_fft,\n",
+ " stem_name=stem_name,\n",
+ " compensation=compensation\n",
+ " )\n",
+ " else:\n",
+ " model_hash = mdx.MDX.get_hash(onnx)\n",
+ " if model_hash in model_params:\n",
+ " mp = model_params.get(model_hash)\n",
+ " mdx_model = mdx.MDX_Model(\n",
+ " device,\n",
+ " dim_f = mp[\"mdx_dim_f_set\"],\n",
+ " dim_t = 2**mp[\"mdx_dim_t_set\"],\n",
+ " n_fft = mp[\"mdx_n_fft_scale_set\"],\n",
+ " stem_name=mp[\"primary_stem\"],\n",
+ " compensation=compensation if not custom_param and compensation is not None else mp[\"compensate\"]\n",
+ " )\n",
+ " return mdx_model\n",
+ "\n",
+ " def run_mdx(onnx, mdx_model,filename,diff=False,suffix=None,diff_suffix=None, denoise=False, m_threads=1):\n",
+ " mdx_sess = mdx.MDX(onnx,mdx_model)\n",
+ " print(f\"Processing: {filename}\")\n",
+ " wave, sr = librosa.load(filename,mono=False, sr=44100)\n",
+ " # normalizing input wave gives better output\n",
+ " peak = max(np.max(wave), abs(np.min(wave)))\n",
+ " wave /= peak\n",
+ " if denoise:\n",
+ " wave_processed = -(mdx_sess.process_wave(-wave, m_threads)) + (mdx_sess.process_wave(wave, m_threads))\n",
+ " wave_processed *= 0.5\n",
+ " else:\n",
+ " wave_processed = mdx_sess.process_wave(wave, m_threads)\n",
+ " # return to previous peak\n",
+ " wave_processed *= peak\n",
+ "\n",
+ " stem_name = mdx_model.stem_name if suffix is None else suffix # use suffix if provided\n",
+ " save_path = f\"{os.path.basename(os.path.splitext(filename)[0])}_{stem_name}.wav\"\n",
+ " save_path = os.path.join(\n",
+ " 'separated',\n",
+ " save_path\n",
+ " )\n",
+ " sf.write(\n",
+ " save_path,\n",
+ " wave_processed.T,\n",
+ " sr\n",
+ " )\n",
+ "\n",
+ " print(f'done, saved to: {save_path}')\n",
+ "\n",
+ " if diff:\n",
+ " diff_stem_name = stem_naming.get(stem_name) if diff_suffix is None else diff_suffix # use suffix if provided\n",
+ " stem_name = f\"{stem_name}_diff\" if diff_stem_name is None else diff_stem_name\n",
+ " save_path = f\"{os.path.basename(os.path.splitext(filename)[0])}_{stem_name}.wav\"\n",
+ " save_path = os.path.join(\n",
+ " 'separated',\n",
+ " save_path\n",
+ " )\n",
+ " sf.write(\n",
+ " save_path,\n",
+ " (-wave_processed.T*mdx_model.compensation)+wave.T,\n",
+ " sr\n",
+ " )\n",
+ " print(f'invert done, saved to: {save_path}')\n",
+ " del mdx_sess, wave_processed, wave\n",
+ " gc.collect()\n",
+ "\n",
+ " def is_valid_url(url):\n",
+ " import re\n",
+ " regex = re.compile(\n",
+ " r'^https?://'\n",
+ " r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?|'\n",
+ " r'localhost|'\n",
+ " r'\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})'\n",
+ " r'(?::\\d+)?'\n",
+ " r'(?:/?|[/?]\\S+)$', re.IGNORECASE)\n",
+ " return url is not None and regex.search(url)\n",
+ "\n",
+ " def download_deezer(link, arl, fmt='FLAC'):\n",
+ " match fmt:\n",
+ " case 'FLAC':\n",
+ " bitrate = TrackFormats.FLAC\n",
+ " case 'MP3_320':\n",
+ " bitrate = TrackFormats.MP3_320\n",
+ " case 'MP3_128':\n",
+ " bitrate = TrackFormats.MP3_128\n",
+ " case _:\n",
+ " bitrate = TrackFormats.MP3_128\n",
+ "\n",
+ " dz = Deezer()\n",
+ " settings = loadSettings('dz_config')\n",
+ " settings['downloadLocation'] = './tracks'\n",
+ " if not dz.login_via_arl(arl.strip()):\n",
+ " raise Exception('Error while logging in with provided ARL.')\n",
+ " downloadObject = generateDownloadObject(dz, link, bitrate)\n",
+ " print(f'Downloading {downloadObject.type}: \"{downloadObject.title}\" by {downloadObject.artist}...',end=' ',flush=True)\n",
+ " Downloader(dz, downloadObject, settings).start()\n",
+ " print(f'done.')\n",
+ "\n",
+ " path_to_audio = []\n",
+ " for file in downloadObject.files:\n",
+ " path_to_audio.append(file[\"path\"])\n",
+ "\n",
+ " return path_to_audio\n",
+ "\n",
+ " def download_link(url):\n",
+ " ydl_opts = {\n",
+ " 'format': 'bestvideo+bestaudio/best',\n",
+ " 'outtmpl': '%(title)s.%(ext)s',\n",
+ " 'nocheckcertificate': True,\n",
+ " 'ignoreerrors': True,\n",
+ " 'no_warnings': True,\n",
+ " 'extractaudio': True,\n",
+ " }\n",
+ " with yt_dlp.YoutubeDL(ydl_opts) as ydl:\n",
+ " result = ydl.extract_info(url, download=True)\n",
+ " download_path = ydl.prepare_filename(result)\n",
+ " return download_path\n",
+ "\n",
+ " print('finished setting up!')\n",
+ " first_cell_ran = True"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "4hd1TzEGCiRo",
+ "cellView": "form"
+ },
+ "outputs": [],
+ "source": [
+ "if 'first_cell_ran' in locals():\n",
+ " os.chdir(mounting_path + '/' + file_folder + '/')\n",
+ " #parameter markdowns-----------------\n",
+ " #@markdown ### Input files\n",
+ " #@markdown track filename: Upload your songs to the \"tracks\" folder. You may provide multiple links/files by spliting them with ;\n",
+ " filename = \"https://deezer.com/album/281108671\" #@param {type:\"string\"}\n",
+ " #@markdown onnx model (if you have your own model, upload it in models folder)\n",
+ " onnx = \"UVR-MDX-NET-Inst_HQ_3.onnx\" #@param [\"Kim_Inst.onnx\", \"Kim_Vocal_1.onnx\", \"Kim_Vocal_2.onnx\", \"kuielab_a_bass.onnx\", \"kuielab_a_drums.onnx\", \"kuielab_a_other.onnx\", \"kuielab_a_vocals.onnx\", \"kuielab_b_bass.onnx\", \"kuielab_b_drums.onnx\", \"kuielab_b_other.onnx\", \"kuielab_b_vocals.onnx\", \"Reverb_HQ_By_FoxJoy.onnx\", \"UVR-MDX-NET-Inst_1.onnx\", \"UVR-MDX-NET-Inst_2.onnx\", \"UVR-MDX-NET-Inst_3.onnx\", \"UVR-MDX-NET-Inst_HQ_1.onnx\", \"UVR-MDX-NET-Inst_HQ_2.onnx\", \"UVR-MDX-NET-Inst_Main.onnx\", \"UVR_MDXNET_1_9703.onnx\", \"UVR_MDXNET_2_9682.onnx\", \"UVR_MDXNET_3_9662.onnx\", \"UVR_MDXNET_9482.onnx\", \"UVR_MDXNET_KARA.onnx\", \"UVR_MDXNET_KARA_2.onnx\", \"UVR_MDXNET_Main.onnx\", \"UVR-MDX-NET-Inst_HQ_3.onnx\", \"UVR-MDX-NET-Voc_FT.onnx\"]{allow-input: true}\n",
+ " #@markdown process all: processes all tracks inside tracks/ folder instead. (filename will be ignored!)\n",
+ " process_all = False # @param{type:\"boolean\"}\n",
+ "\n",
+ "\n",
+ " #@markdown ### Settings\n",
+ " #@markdown invert: get difference between input and output (e.g get Instrumental out of Vocals)\n",
+ " invert = True # @param{type:\"boolean\"}\n",
+ " #@markdown denoise: get rid of MDX noise. (This processes input track twice)\n",
+ " denoise = True # @param{type:\"boolean\"}\n",
+ " #@markdown m_threads: like batch size, processes input wave in n threads. (beneficial for CPU)\n",
+ " m_threads = 2 #@param {type:\"slider\", min:1, max:8, step:1}\n",
+ "\n",
+ " #@markdown ### Custom model parameters (Only use this if you're using new/unofficial/custom models)\n",
+ " #@markdown Use custom model parameters. (Default: unchecked, or auto)\n",
+ " use_custom_parameter = False # @param{type:\"boolean\"}\n",
+ " #@markdown Output file suffix (usually the stem name e.g Vocals)\n",
+ " suffix = \"Vocals_custom\" #@param [\"Vocals\", \"Drums\", \"Bass\", \"Other\"]{allow-input: true}\n",
+ " suffix_invert = \"Instrumental_custom\" #@param [\"Instrumental\", \"Drumless\", \"Bassless\", \"Instruments\"]{allow-input: true}\n",
+ " #@markdown Model parameters\n",
+ " dim_f = 3072 #@param {type: \"integer\"}\n",
+ " dim_t = 256 #@param {type: \"integer\"}\n",
+ " n_fft = 6144 #@param {type: \"integer\"}\n",
+ " #@markdown use custom compensation: only if you have your own compensation value for your model. this still apply even if you don't have use_custom_parameter checked (Default: unchecked, or auto)\n",
+ " use_custom_compensation = False # @param{type:\"boolean\"}\n",
+ " compensation = 1.000 #@param {type: \"number\"}\n",
+ "\n",
+ " #@markdown ### Extras\n",
+ " #@markdown Deezer arl: paste your ARL here for deezer tracks directly!\n",
+ " arl = \"\" #@param {type:\"string\"}\n",
+ " #@markdown Track format: select track quality/format\n",
+ " track_format = \"FLAC\" #@param [\"FLAC\",\"MP3_320\",\"MP3_128\"]\n",
+ " #@markdown Print settings being used in the run\n",
+ " print_settings = True # @param{type:\"boolean\"}\n",
+ "\n",
+ "\n",
+ "\n",
+ " onnx = id_to_ptm(onnx)\n",
+ " compensation = compensation if use_custom_compensation or use_custom_parameter else None\n",
+ " mdx_model = prepare_mdx(use_custom_parameter, dim_f, dim_t, n_fft, compensation=compensation)\n",
+ "\n",
+ " filename_split = filename.split(';')\n",
+ "\n",
+ " usable_files = []\n",
+ "\n",
+ " if not process_all:\n",
+ " for fn in filename_split:\n",
+ " fn = fn.strip()\n",
+ " if is_valid_url(fn):\n",
+ " dm, ltype, lid = deemix.parseLink(fn)\n",
+ " if ltype and lid:\n",
+ " usable_files += download_deezer(fn, arl, track_format)\n",
+ " else:\n",
+ " print('downloading link...',end=' ')\n",
+ " usable_files+=[download_link(fn)]\n",
+ " print('done')\n",
+ " else:\n",
+ " usable_files.append(os.path.join('tracks',fn))\n",
+ " else:\n",
+ " for fn in glob.glob('tracks/*'):\n",
+ " usable_files.append(fn)\n",
+ " for filename in usable_files:\n",
+ " suffix_naming = suffix if use_custom_parameter else None\n",
+ " diff_suffix_naming = suffix_invert if use_custom_parameter else None\n",
+ " run_mdx(onnx, mdx_model, filename, diff=invert,suffix=suffix_naming,diff_suffix=diff_suffix_naming,denoise=denoise)\n",
+ "\n",
+ " if print_settings:\n",
+ " print()\n",
+ " print('[MDX-Net_Colab settings used]')\n",
+ " print(f'Model used: {onnx}')\n",
+ " print(f'Model MD5: {mdx.MDX.get_hash(onnx)}')\n",
+ " print(f'Using de-noise: {denoise}')\n",
+ " print(f'Model parameters:')\n",
+ " print(f' -dim_f: {mdx_model.dim_f}')\n",
+ " print(f' -dim_t: {mdx_model.dim_t}')\n",
+ " print(f' -n_fft: {mdx_model.n_fft}')\n",
+ " print(f' -compensation: {mdx_model.compensation}')\n",
+ " print()\n",
+ " print('[Input file]')\n",
+ " print('filename(s): ')\n",
+ " for filename in usable_files:\n",
+ " print(f' -{filename}')\n",
+ "\n",
+ " del mdx_model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# Guide\n",
+ "\n",
+ "This tutorial guide will walk you through the steps to use the features of this Colab notebook.\n",
+ "\n",
+ "## Mount Drive\n",
+ "\n",
+ "To mount your Google Drive, follow these steps:\n",
+ "\n",
+ "1. Check the box next to \"MountDrive\" if you want to mount Google Drive.\n",
+ "2. Modify the \"mounting_path\" if you want to specify a different path for the drive to be mounted. **Note:** Be cautious when modifying this path as it can cause issues if not done properly.\n",
+ "3. Check the box next to \"Force update and disregard local changes\" if you want to discard all local modifications in your repository and replace the files with the versions from the original commit.\n",
+ "4. Check the box next to \"Auto Update\" if you want to automatically update without discarding your changes. Leave it unchecked if you want to manually update.\n",
+ "\n",
+ "## Input Files\n",
+ "\n",
+ "To upload your songs, follow these steps:\n",
+ "\n",
+ "1. Specify the \"track filename\" for your songs. You can provide multiple links or files by separating them with a semicolon (;).\n",
+ "2. Upload your songs to the \"tracks\" folder.\n",
+ "\n",
+ "## ONNX Model\n",
+ "\n",
+ "If you have your own ONNX model, follow these steps:\n",
+ "\n",
+ "1. Upload your model to the \"models\" folder.\n",
+ "2. Specify the \"onnx\" filename for your model.\n",
+ "\n",
+ "## Processing\n",
+ "\n",
+ "To process your tracks, follow these steps:\n",
+ "\n",
+ "1. If you want to process all tracks inside the \"tracks\" folder, check the box next to \"process_all\" and ignore the \"filename\" field.\n",
+ "2. Specify any additional settings you want:\n",
+ " - Check the box next to \"invert\" to get the difference between input and output (e.g., get Instrumental out of Vocals).\n",
+ " - Check the box next to \"denoise\" to get rid of MDX noise. This processes the input track twice.\n",
+ " - Specify custom model parameters only if you're using new/unofficial/custom models. Use the \"use_custom_parameter\" checkbox to enable this feature.\n",
+ " - Specify the output file suffix, which is usually the stem name (e.g., Vocals). Use the \"suffix\" field to specify the suffix for normal processing and the \"suffix_invert\" field for inverted processing.\n",
+ "\n",
+ "## Model Parameters\n",
+ "\n",
+ "Specify the following custom model parameters if applicable:\n",
+ "\n",
+ "- \"dim_f\": The value for the `dim_f` parameter.\n",
+ "- \"dim_t\": The value for the `dim_t` parameter.\n",
+ "- \"n_fft\": The value for the `n_fft` parameter.\n",
+ "- Check the box next to \"use_custom_compensation\" if you have your own compensation value for your model. Specify the compensation value in the \"compensation\" field.\n",
+ "\n",
+ "## Extras\n",
+ "\n",
+ "If you're working with Deezer tracks, paste your ARL (Authentication Request Library) in the \"arl\" field to directly access the tracks.\n",
+ "\n",
+ "Specify the \"Track format\" by selecting the desired quality/format for the track.\n",
+ "\n",
+ "To print the settings being used in the run, check the box next to \"print_settings\".\n",
+ "\n",
+ "That's it! You're now ready to use this Colab notebook. Enjoy!\n",
+ "\n",
+ "## For more detailed guide, proceed to this link.\n",
+ "credits: (discord) deton24"
+ ],
+ "metadata": {
+ "id": "tMVwX5RhZSRP"
+ }
+ }
+ ],
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "gpuType": "T4",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/MDXNet.py b/MDXNet.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b7eb43844ad0d4f9ce61287ccf9a8a4206d3853
--- /dev/null
+++ b/MDXNet.py
@@ -0,0 +1,272 @@
+import soundfile as sf
+import torch, pdb, os, warnings, librosa
+import numpy as np
+import onnxruntime as ort
+from tqdm import tqdm
+import torch
+
+dim_c = 4
+
+
+class Conv_TDF_net_trim:
+ def __init__(
+ self, device, model_name, target_name, L, dim_f, dim_t, n_fft, hop=1024
+ ):
+ super(Conv_TDF_net_trim, self).__init__()
+
+ self.dim_f = dim_f
+ self.dim_t = 2**dim_t
+ self.n_fft = n_fft
+ self.hop = hop
+ self.n_bins = self.n_fft // 2 + 1
+ self.chunk_size = hop * (self.dim_t - 1)
+ self.window = torch.hann_window(window_length=self.n_fft, periodic=True).to(
+ device
+ )
+ self.target_name = target_name
+ self.blender = "blender" in model_name
+
+ out_c = dim_c * 4 if target_name == "*" else dim_c
+ self.freq_pad = torch.zeros(
+ [1, out_c, self.n_bins - self.dim_f, self.dim_t]
+ ).to(device)
+
+ self.n = L // 2
+
+ def stft(self, x):
+ x = x.reshape([-1, self.chunk_size])
+ x = torch.stft(
+ x,
+ n_fft=self.n_fft,
+ hop_length=self.hop,
+ window=self.window,
+ center=True,
+ return_complex=True,
+ )
+ x = torch.view_as_real(x)
+ x = x.permute([0, 3, 1, 2])
+ x = x.reshape([-1, 2, 2, self.n_bins, self.dim_t]).reshape(
+ [-1, dim_c, self.n_bins, self.dim_t]
+ )
+ return x[:, :, : self.dim_f]
+
+ def istft(self, x, freq_pad=None):
+ freq_pad = (
+ self.freq_pad.repeat([x.shape[0], 1, 1, 1])
+ if freq_pad is None
+ else freq_pad
+ )
+ x = torch.cat([x, freq_pad], -2)
+ c = 4 * 2 if self.target_name == "*" else 2
+ x = x.reshape([-1, c, 2, self.n_bins, self.dim_t]).reshape(
+ [-1, 2, self.n_bins, self.dim_t]
+ )
+ x = x.permute([0, 2, 3, 1])
+ x = x.contiguous()
+ x = torch.view_as_complex(x)
+ x = torch.istft(
+ x, n_fft=self.n_fft, hop_length=self.hop, window=self.window, center=True
+ )
+ return x.reshape([-1, c, self.chunk_size])
+
+
+def get_models(device, dim_f, dim_t, n_fft):
+ return Conv_TDF_net_trim(
+ device=device,
+ model_name="Conv-TDF",
+ target_name="vocals",
+ L=11,
+ dim_f=dim_f,
+ dim_t=dim_t,
+ n_fft=n_fft,
+ )
+
+
+warnings.filterwarnings("ignore")
+cpu = torch.device("cpu")
+if torch.cuda.is_available():
+ device = torch.device("cuda:0")
+elif torch.backends.mps.is_available():
+ device = torch.device("mps")
+else:
+ device = torch.device("cpu")
+
+
+class Predictor:
+ def __init__(self, args):
+ self.args = args
+ self.model_ = get_models(
+ device=cpu, dim_f=args.dim_f, dim_t=args.dim_t, n_fft=args.n_fft
+ )
+ self.model = ort.InferenceSession(
+ os.path.join(args.onnx, self.model_.target_name + ".onnx"),
+ providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
+ )
+ print("onnx load done")
+
+ def demix(self, mix):
+ samples = mix.shape[-1]
+ margin = self.args.margin
+ chunk_size = self.args.chunks * 44100
+ assert not margin == 0, "margin cannot be zero!"
+ if margin > chunk_size:
+ margin = chunk_size
+
+ segmented_mix = {}
+
+ if self.args.chunks == 0 or samples < chunk_size:
+ chunk_size = samples
+
+ counter = -1
+ for skip in range(0, samples, chunk_size):
+ counter += 1
+
+ s_margin = 0 if counter == 0 else margin
+ end = min(skip + chunk_size + margin, samples)
+
+ start = skip - s_margin
+
+ segmented_mix[skip] = mix[:, start:end].copy()
+ if end == samples:
+ break
+
+ sources = self.demix_base(segmented_mix, margin_size=margin)
+ """
+ mix:(2,big_sample)
+ segmented_mix:offset->(2,small_sample)
+ sources:(1,2,big_sample)
+ """
+ return sources
+
+ def demix_base(self, mixes, margin_size):
+ chunked_sources = []
+ progress_bar = tqdm(total=len(mixes))
+ progress_bar.set_description("Processing")
+ for mix in mixes:
+ cmix = mixes[mix]
+ sources = []
+ n_sample = cmix.shape[1]
+ model = self.model_
+ trim = model.n_fft // 2
+ gen_size = model.chunk_size - 2 * trim
+ pad = gen_size - n_sample % gen_size
+ mix_p = np.concatenate(
+ (np.zeros((2, trim)), cmix, np.zeros((2, pad)), np.zeros((2, trim))), 1
+ )
+ mix_waves = []
+ i = 0
+ while i < n_sample + pad:
+ waves = np.array(mix_p[:, i : i + model.chunk_size])
+ mix_waves.append(waves)
+ i += gen_size
+ mix_waves = torch.tensor(mix_waves, dtype=torch.float32).to(cpu)
+ with torch.no_grad():
+ _ort = self.model
+ spek = model.stft(mix_waves)
+ if self.args.denoise:
+ spec_pred = (
+ -_ort.run(None, {"input": -spek.cpu().numpy()})[0] * 0.5
+ + _ort.run(None, {"input": spek.cpu().numpy()})[0] * 0.5
+ )
+ tar_waves = model.istft(torch.tensor(spec_pred))
+ else:
+ tar_waves = model.istft(
+ torch.tensor(_ort.run(None, {"input": spek.cpu().numpy()})[0])
+ )
+ tar_signal = (
+ tar_waves[:, :, trim:-trim]
+ .transpose(0, 1)
+ .reshape(2, -1)
+ .numpy()[:, :-pad]
+ )
+
+ start = 0 if mix == 0 else margin_size
+ end = None if mix == list(mixes.keys())[::-1][0] else -margin_size
+ if margin_size == 0:
+ end = None
+ sources.append(tar_signal[:, start:end])
+
+ progress_bar.update(1)
+
+ chunked_sources.append(sources)
+ _sources = np.concatenate(chunked_sources, axis=-1)
+ # del self.model
+ progress_bar.close()
+ return _sources
+
+ def prediction(self, m, vocal_root, others_root, format):
+ os.makedirs(vocal_root, exist_ok=True)
+ os.makedirs(others_root, exist_ok=True)
+ basename = os.path.basename(m)
+ mix, rate = librosa.load(m, mono=False, sr=44100)
+ if mix.ndim == 1:
+ mix = np.asfortranarray([mix, mix])
+ mix = mix.T
+ sources = self.demix(mix.T)
+ opt = sources[0].T
+ if format in ["wav", "flac"]:
+ sf.write(
+ "%s/%s_main_vocal.%s" % (vocal_root, basename, format), mix - opt, rate
+ )
+ sf.write("%s/%s_others.%s" % (others_root, basename, format), opt, rate)
+ else:
+ path_vocal = "%s/%s_main_vocal.wav" % (vocal_root, basename)
+ path_other = "%s/%s_others.wav" % (others_root, basename)
+ sf.write(path_vocal, mix - opt, rate)
+ sf.write(path_other, opt, rate)
+ if os.path.exists(path_vocal):
+ os.system(
+ "ffmpeg -i %s -vn %s -q:a 2 -y"
+ % (path_vocal, path_vocal[:-4] + ".%s" % format)
+ )
+ if os.path.exists(path_other):
+ os.system(
+ "ffmpeg -i %s -vn %s -q:a 2 -y"
+ % (path_other, path_other[:-4] + ".%s" % format)
+ )
+
+
+class MDXNetDereverb:
+ def __init__(self, chunks):
+ self.onnx = "uvr5_weights/onnx_dereverb_By_FoxJoy"
+ self.shifts = 10 #'Predict with randomised equivariant stabilisation'
+ self.mixing = "min_mag" # ['default','min_mag','max_mag']
+ self.chunks = chunks
+ self.margin = 44100
+ self.dim_t = 9
+ self.dim_f = 3072
+ self.n_fft = 6144
+ self.denoise = True
+ self.pred = Predictor(self)
+
+ def _path_audio_(self, input, vocal_root, others_root, format):
+ self.pred.prediction(input, vocal_root, others_root, format)
+
+
+if __name__ == "__main__":
+ dereverb = MDXNetDereverb(15)
+ from time import time as ttime
+
+ t0 = ttime()
+ dereverb._path_audio_(
+ "雪雪伴奏对消HP5.wav",
+ "vocal",
+ "others",
+ )
+ t1 = ttime()
+ print(t1 - t0)
+
+
+"""
+
+runtime\python.exe MDXNet.py
+
+6G:
+15/9:0.8G->6.8G
+14:0.8G->6.5G
+25:炸
+
+half15:0.7G->6.6G,22.69s
+fp32-15:0.7G->6.6G,20.85s
+
+"""
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..44de020e6feb7fcd58016d7c3c736681f533b597
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,63 @@
+.PHONY:
+.ONESHELL:
+
+help: ## Show this help and exit
+ @grep -hE '^[A-Za-z0-9_ \-]*?:.*##.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+install: ## Install dependencies (Do everytime you start up a paperspace machine)
+ apt-get -y install build-essential python3-dev ffmpeg
+ pip install --upgrade setuptools wheel
+ pip install --upgrade pip
+ pip install faiss-gpu fairseq gradio ffmpeg ffmpeg-python praat-parselmouth pyworld numpy==1.23.5 numba==0.56.4 librosa==0.9.1
+ pip install -r requirements.txt
+ pip install --upgrade lxml
+ apt-get update
+ apt -y install -qq aria2
+
+basev1: ## Download version 1 pre-trained models (Do only once after cloning the fork)
+ mkdir -p pretrained uvr5_weights
+ git pull
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/D32k.pth -d pretrained -o D32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/D40k.pth -d pretrained -o D40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/D48k.pth -d pretrained -o D48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/G32k.pth -d pretrained -o G32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/G40k.pth -d pretrained -o G40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/G48k.pth -d pretrained -o G48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/f0D32k.pth -d pretrained -o f0D32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/f0D40k.pth -d pretrained -o f0D40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/f0D48k.pth -d pretrained -o f0D48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/f0G32k.pth -d pretrained -o f0G32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/f0G40k.pth -d pretrained -o f0G40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained/f0G48k.pth -d pretrained -o f0G48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP2-人声vocals+非人声instrumentals.pth -d uvr5_weights -o HP2-人声vocals+非人声instrumentals.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP5-主旋律人声vocals+其他instrumentals.pth -d uvr5_weights -o HP5-主旋律人声vocals+其他instrumentals.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/hubert_base.pt -d ./ -o hubert_base.pt
+
+basev2: ## Download version 2 pre-trained models (Do only once after cloning the fork)
+ mkdir -p pretrained_v2 uvr5_weights
+ git pull
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/D32k.pth -d pretrained_v2 -o D32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/D40k.pth -d pretrained_v2 -o D40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/D48k.pth -d pretrained_v2 -o D48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/G32k.pth -d pretrained_v2 -o G32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/G40k.pth -d pretrained_v2 -o G40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/G48k.pth -d pretrained_v2 -o G48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0D32k.pth -d pretrained_v2 -o f0D32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0D40k.pth -d pretrained_v2 -o f0D40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0D48k.pth -d pretrained_v2 -o f0D48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0G32k.pth -d pretrained_v2 -o f0G32k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0G40k.pth -d pretrained_v2 -o f0G40k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0G48k.pth -d pretrained_v2 -o f0G48k.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP2-人声vocals+非人声instrumentals.pth -d uvr5_weights -o HP2-人声vocals+非人声instrumentals.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP5-主旋律人声vocals+其他instrumentals.pth -d uvr5_weights -o HP5-主旋律人声vocals+其他instrumentals.pth
+ aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/hubert_base.pt -d ./ -o hubert_base.pt
+
+run-ui: ## Run the python GUI
+ python infer-web.py --paperspace --pycmd python
+
+run-cli: ## Run the python CLI
+ python infer-web.py --pycmd python --is_cli
+
+tensorboard: ## Start the tensorboard (Run on separate terminal)
+ echo https://tensorboard-$$(hostname).clg07azjl.paperspacegradient.com
+ tensorboard --logdir logs --bind_all
\ No newline at end of file
diff --git a/README.md b/README.md
index a1989976129f2f543086e73dd5387e572acb4a0e..9d8914cd05791e4f8db6267eb2a5fe2133e22e58 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,10 @@
---
+title: RVC Inference HF
+emoji: 👀
+colorFrom: green
+colorTo: green
sdk: gradio
+sdk_version: 3.43.2
+app_file: app.py
+pinned: false
---
\ No newline at end of file
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..d54981948ff97fca229a7727aaa7823603d6395a
--- /dev/null
+++ b/app.py
@@ -0,0 +1,3154 @@
+import os, sys
+os.system("pip install pyworld") # ==0.3.3
+
+now_dir = os.getcwd()
+sys.path.append(now_dir)
+os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
+os.environ["OPENBLAS_NUM_THREADS"] = "1"
+os.environ["no_proxy"] = "localhost, 127.0.0.1, ::1"
+
+# Download models
+shell_script = './tools/dlmodels.sh'
+os.system(f'chmod +x {shell_script}')
+os.system('apt install git-lfs')
+os.system('git lfs install')
+os.system('apt-get -y install aria2')
+os.system('aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/hubert_base.pt -d . -o hubert_base.pt')
+try:
+ return_code = os.system(shell_script)
+ if return_code == 0:
+ print("Shell script executed successfully.")
+ else:
+ print(f"Shell script failed with return code {return_code}")
+except Exception as e:
+ print(f"An error occurred: {e}")
+
+
+import logging
+import shutil
+import threading
+import lib.globals.globals as rvc_globals
+from LazyImport import lazyload
+import mdx
+from mdx_processing_script import get_model_list,id_to_ptm,prepare_mdx,run_mdx
+math = lazyload('math')
+import traceback
+import warnings
+tensorlowest = lazyload('tensorlowest')
+from random import shuffle
+from subprocess import Popen
+from time import sleep
+import json
+import pathlib
+
+import fairseq
+logging.getLogger("faiss").setLevel(logging.WARNING)
+import faiss
+gr = lazyload("gradio")
+np = lazyload("numpy")
+torch = lazyload('torch')
+re = lazyload('regex')
+SF = lazyload("soundfile")
+SFWrite = SF.write
+from dotenv import load_dotenv
+from sklearn.cluster import MiniBatchKMeans
+import datetime
+
+
+from glob import glob1
+import signal
+from signal import SIGTERM
+import librosa
+
+from configs.config import Config
+from i18n import I18nAuto
+from infer.lib.train.process_ckpt import (
+ change_info,
+ extract_small_model,
+ merge,
+ show_info,
+)
+#from infer.modules.uvr5.modules import uvr
+from infer.modules.vc.modules import VC
+from infer.modules.vc.utils import *
+from infer.modules.vc.pipeline import Pipeline
+import lib.globals.globals as rvc_globals
+math = lazyload('math')
+ffmpeg = lazyload('ffmpeg')
+import nltk
+nltk.download('punkt', quiet=True)
+from nltk.tokenize import sent_tokenize
+from bark import SAMPLE_RATE
+
+import easy_infer
+import audioEffects
+from infer.lib.csvutil import CSVutil
+
+from lib.infer_pack.models import (
+ SynthesizerTrnMs256NSFsid,
+ SynthesizerTrnMs256NSFsid_nono,
+ SynthesizerTrnMs768NSFsid,
+ SynthesizerTrnMs768NSFsid_nono,
+)
+from lib.infer_pack.models_onnx import SynthesizerTrnMsNSFsidM
+from infer_uvr5 import _audio_pre_, _audio_pre_new
+from MDXNet import MDXNetDereverb
+from infer.lib.audio import load_audio
+
+
+from sklearn.cluster import MiniBatchKMeans
+
+import time
+import csv
+
+from shlex import quote as SQuote
+
+
+
+
+RQuote = lambda val: SQuote(str(val))
+
+tmp = os.path.join(now_dir, "TEMP")
+runtime_dir = os.path.join(now_dir, "runtime/Lib/site-packages")
+directories = ['logs', 'audios', 'datasets', 'weights', 'audio-others' , 'audio-outputs']
+
+shutil.rmtree(tmp, ignore_errors=True)
+shutil.rmtree("%s/runtime/Lib/site-packages/infer_pack" % (now_dir), ignore_errors=True)
+shutil.rmtree("%s/runtime/Lib/site-packages/uvr5_pack" % (now_dir), ignore_errors=True)
+
+os.makedirs(tmp, exist_ok=True)
+for folder in directories:
+ os.makedirs(os.path.join(now_dir, folder), exist_ok=True)
+
+
+os.makedirs(tmp, exist_ok=True)
+os.makedirs(os.path.join(now_dir, "logs"), exist_ok=True)
+os.makedirs(os.path.join(now_dir, "assets/weights"), exist_ok=True)
+os.environ["TEMP"] = tmp
+warnings.filterwarnings("ignore")
+torch.manual_seed(114514)
+logging.getLogger("numba").setLevel(logging.WARNING)
+
+logger = logging.getLogger(__name__)
+
+
+if not os.path.isdir("csvdb/"):
+ os.makedirs("csvdb")
+ frmnt, stp = open("csvdb/formanting.csv", "w"), open("csvdb/stop.csv", "w")
+ frmnt.close()
+ stp.close()
+
+global DoFormant, Quefrency, Timbre
+
+try:
+ DoFormant, Quefrency, Timbre = CSVutil("csvdb/formanting.csv", "r", "formanting")
+ DoFormant = (
+ lambda DoFormant: True
+ if DoFormant.lower() == "true"
+ else (False if DoFormant.lower() == "false" else DoFormant)
+ )(DoFormant)
+except (ValueError, TypeError, IndexError):
+ DoFormant, Quefrency, Timbre = False, 1.0, 1.0
+ CSVutil("csvdb/formanting.csv", "w+", "formanting", DoFormant, Quefrency, Timbre)
+
+load_dotenv()
+config = Config()
+vc = VC(config)
+
+if config.dml == True:
+
+ def forward_dml(ctx, x, scale):
+ ctx.scale = scale
+ res = x.clone().detach()
+ return res
+
+ fairseq.modules.grad_multiply.GradMultiply.forward = forward_dml
+
+i18n = I18nAuto()
+i18n.print()
+# 判断是否有能用来训练和加速推理的N卡
+ngpu = torch.cuda.device_count()
+gpu_infos = []
+mem = []
+if_gpu_ok = False
+
+isinterrupted = 0
+
+
+if torch.cuda.is_available() or ngpu != 0:
+ for i in range(ngpu):
+ gpu_name = torch.cuda.get_device_name(i)
+ if any(
+ value in gpu_name.upper()
+ for value in [
+ "10",
+ "16",
+ "20",
+ "30",
+ "40",
+ "A2",
+ "A3",
+ "A4",
+ "P4",
+ "A50",
+ "500",
+ "A60",
+ "70",
+ "80",
+ "90",
+ "M4",
+ "T4",
+ "TITAN",
+ ]
+ ):
+ # A10#A100#V100#A40#P40#M40#K80#A4500
+ if_gpu_ok = True # 至少有一张能用的N卡
+ gpu_infos.append("%s\t%s" % (i, gpu_name))
+ mem.append(
+ int(
+ torch.cuda.get_device_properties(i).total_memory
+ / 1024
+ / 1024
+ / 1024
+ + 0.4
+ )
+ )
+if if_gpu_ok and len(gpu_infos) > 0:
+ gpu_info = "\n".join(gpu_infos)
+ default_batch_size = min(mem) // 2
+else:
+ gpu_info = "Unfortunately, there is no compatible GPU available to support your training."
+ default_batch_size = 1
+gpus = "-".join([i[0] for i in gpu_infos])
+
+class ToolButton(gr.Button, gr.components.FormComponent):
+ """Small button with single emoji as text, fits inside gradio forms"""
+
+ def __init__(self, **kwargs):
+ super().__init__(variant="tool", **kwargs)
+
+ def get_block_name(self):
+ return "button"
+
+
+hubert_model = None
+weight_root = os.getenv("weight_root")
+weight_uvr5_root = os.getenv("weight_uvr5_root")
+index_root = os.getenv("index_root")
+datasets_root = "datasets"
+fshift_root = "formantshiftcfg"
+audio_root = "audios"
+audio_others_root = "audio-others"
+
+sup_audioext = {'wav', 'mp3', 'flac', 'ogg', 'opus',
+ 'm4a', 'mp4', 'aac', 'alac', 'wma',
+ 'aiff', 'webm', 'ac3'}
+
+names = [os.path.join(root, file)
+ for root, _, files in os.walk(weight_root)
+ for file in files
+ if file.endswith((".pth", ".onnx"))]
+
+indexes_list = [os.path.join(root, name)
+ for root, _, files in os.walk(index_root, topdown=False)
+ for name in files
+ if name.endswith(".index") and "trained" not in name]
+
+audio_paths = [os.path.join(root, name)
+ for root, _, files in os.walk(audio_root, topdown=False)
+ for name in files
+ if name.endswith(tuple(sup_audioext))]
+
+audio_others_paths = [os.path.join(root, name)
+ for root, _, files in os.walk(audio_others_root, topdown=False)
+ for name in files
+ if name.endswith(tuple(sup_audioext))]
+
+uvr5_names = [name.replace(".pth", "")
+ for name in os.listdir(weight_uvr5_root)
+ if name.endswith(".pth") or "onnx" in name]
+
+
+check_for_name = lambda: sorted(names)[0] if names else ''
+
+datasets=[]
+for foldername in os.listdir(os.path.join(now_dir, datasets_root)):
+ if "." not in foldername:
+ datasets.append(os.path.join(easy_infer.find_folder_parent(".","pretrained"),"datasets",foldername))
+
+def get_dataset():
+ if len(datasets) > 0:
+ return sorted(datasets)[0]
+ else:
+ return ''
+
+def update_model_choices(select_value):
+ model_ids = get_model_list()
+ model_ids_list = list(model_ids)
+ if select_value == "VR":
+ return {"choices": uvr5_names, "__type__": "update"}
+ elif select_value == "MDX":
+ return {"choices": model_ids_list, "__type__": "update"}
+
+set_bark_voice = easy_infer.get_bark_voice()
+set_edge_voice = easy_infer.get_edge_voice()
+
+def update_tts_methods_voice(select_value):
+ #["Edge-tts", "RVG-tts", "Bark-tts"]
+ if select_value == "Edge-tts":
+ return {"choices": set_edge_voice, "value": "", "__type__": "update"}
+ elif select_value == "Bark-tts":
+ return {"choices": set_bark_voice, "value": "", "__type__": "update"}
+
+
+def update_dataset_list(name):
+ new_datasets = []
+ for foldername in os.listdir(os.path.join(now_dir, datasets_root)):
+ if "." not in foldername:
+ new_datasets.append(os.path.join(easy_infer.find_folder_parent(".","pretrained"),"datasets",foldername))
+ return gr.Dropdown.update(choices=new_datasets)
+
+def get_indexes():
+ indexes_list = [
+ os.path.join(dirpath, filename)
+ for dirpath, _, filenames in os.walk(index_root)
+ for filename in filenames
+ if filename.endswith(".index") and "trained" not in filename
+ ]
+
+ return indexes_list if indexes_list else ''
+
+def get_fshift_presets():
+ fshift_presets_list = [
+ os.path.join(dirpath, filename)
+ for dirpath, _, filenames in os.walk(fshift_root)
+ for filename in filenames
+ if filename.endswith(".txt")
+ ]
+
+ return fshift_presets_list if fshift_presets_list else ''
+
+import soundfile as sf
+
+def generate_output_path(output_folder, base_name, extension):
+ # Generar un nombre único para el archivo de salida
+ index = 1
+ while True:
+ output_path = os.path.join(output_folder, f"{base_name}_{index}.{extension}")
+ if not os.path.exists(output_path):
+ return output_path
+ index += 1
+
+def combine_and_save_audios(audio1_path, audio2_path, output_path, volume_factor_audio1, volume_factor_audio2):
+ audio1, sr1 = librosa.load(audio1_path, sr=None)
+ audio2, sr2 = librosa.load(audio2_path, sr=None)
+
+ # Alinear las tasas de muestreo
+ if sr1 != sr2:
+ if sr1 > sr2:
+ audio2 = librosa.resample(audio2, orig_sr=sr2, target_sr=sr1)
+ else:
+ audio1 = librosa.resample(audio1, orig_sr=sr1, target_sr=sr2)
+
+ # Ajustar los audios para que tengan la misma longitud
+ target_length = min(len(audio1), len(audio2))
+ audio1 = librosa.util.fix_length(audio1, target_length)
+ audio2 = librosa.util.fix_length(audio2, target_length)
+
+ # Ajustar el volumen de los audios multiplicando por el factor de ganancia
+ if volume_factor_audio1 != 1.0:
+ audio1 *= volume_factor_audio1
+ if volume_factor_audio2 != 1.0:
+ audio2 *= volume_factor_audio2
+
+ # Combinar los audios
+ combined_audio = audio1 + audio2
+
+ sf.write(output_path, combined_audio, sr1)
+
+# Resto de tu código...
+
+# Define función de conversión llamada por el botón
+def audio_combined(audio1_path, audio2_path, volume_factor_audio1=1.0, volume_factor_audio2=1.0, reverb_enabled=False, compressor_enabled=False, noise_gate_enabled=False):
+ output_folder = os.path.join(now_dir, "audio-outputs")
+ os.makedirs(output_folder, exist_ok=True)
+
+ # Generar nombres únicos para los archivos de salida
+ base_name = "combined_audio"
+ extension = "wav"
+ output_path = generate_output_path(output_folder, base_name, extension)
+ print(reverb_enabled)
+ print(compressor_enabled)
+ print(noise_gate_enabled)
+
+ if reverb_enabled or compressor_enabled or noise_gate_enabled:
+ # Procesa el primer audio con los efectos habilitados
+ base_name = "effect_audio"
+ output_path = generate_output_path(output_folder, base_name, extension)
+ processed_audio_path = audioEffects.process_audio(audio2_path, output_path, reverb_enabled, compressor_enabled, noise_gate_enabled)
+ base_name = "combined_audio"
+ output_path = generate_output_path(output_folder, base_name, extension)
+ # Combina el audio procesado con el segundo audio usando audio_combined
+ combine_and_save_audios(audio1_path, processed_audio_path, output_path, volume_factor_audio1, volume_factor_audio2)
+
+ return i18n("Conversion complete!"), output_path
+ else:
+ base_name = "combined_audio"
+ output_path = generate_output_path(output_folder, base_name, extension)
+ # No hay efectos habilitados, combina directamente los audios sin procesar
+ combine_and_save_audios(audio1_path, audio2_path, output_path, volume_factor_audio1, volume_factor_audio2)
+
+ return i18n("Conversion complete!"), output_path
+
+
+
+
+def uvr(model_name, inp_root, save_root_vocal, paths, save_root_ins, agg, format0,architecture):
+ infos = []
+ if architecture == "VR":
+ try:
+ inp_root, save_root_vocal, save_root_ins = [x.strip(" ").strip('"').strip("\n").strip('"').strip(" ") for x in [inp_root, save_root_vocal, save_root_ins]]
+ usable_files = [os.path.join(inp_root, file)
+ for file in os.listdir(inp_root)
+ if file.endswith(tuple(sup_audioext))]
+
+
+ pre_fun = MDXNetDereverb(15) if model_name == "onnx_dereverb_By_FoxJoy" else (_audio_pre_ if "DeEcho" not in model_name else _audio_pre_new)(
+ agg=int(agg),
+ model_path=os.path.join(weight_uvr5_root, model_name + ".pth"),
+ device=config.device,
+ is_half=config.is_half,
+ )
+
+ try:
+ if paths != None:
+ paths = [path.name for path in paths]
+ else:
+ paths = usable_files
+
+ except:
+ traceback.print_exc()
+ paths = usable_files
+ print(paths)
+ for path in paths:
+ inp_path = os.path.join(inp_root, path)
+ need_reformat, done = 1, 0
+
+ try:
+ info = ffmpeg.probe(inp_path, cmd="ffprobe")
+ if info["streams"][0]["channels"] == 2 and info["streams"][0]["sample_rate"] == "44100":
+ need_reformat = 0
+ pre_fun._path_audio_(inp_path, save_root_ins, save_root_vocal, format0)
+ done = 1
+ except:
+ traceback.print_exc()
+
+ if need_reformat:
+ tmp_path = f"{tmp}/{os.path.basename(RQuote(inp_path))}.reformatted.wav"
+ os.system(f"ffmpeg -i {RQuote(inp_path)} -vn -acodec pcm_s16le -ac 2 -ar 44100 {RQuote(tmp_path)} -y")
+ inp_path = tmp_path
+
+ try:
+ if not done:
+ pre_fun._path_audio_(inp_path, save_root_ins, save_root_vocal, format0)
+ infos.append(f"{os.path.basename(inp_path)}->Success")
+ yield "\n".join(infos)
+ except:
+ infos.append(f"{os.path.basename(inp_path)}->{traceback.format_exc()}")
+ yield "\n".join(infos)
+ except:
+ infos.append(traceback.format_exc())
+ yield "\n".join(infos)
+ finally:
+ try:
+ if model_name == "onnx_dereverb_By_FoxJoy":
+ del pre_fun.pred.model
+ del pre_fun.pred.model_
+ else:
+ del pre_fun.model
+
+ del pre_fun
+ except: traceback.print_exc()
+
+ print("clean_empty_cache")
+
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
+
+ yield "\n".join(infos)
+ elif architecture == "MDX":
+ try:
+ infos.append(i18n("Starting audio conversion... (This might take a moment)"))
+ yield "\n".join(infos)
+ inp_root, save_root_vocal, save_root_ins = [x.strip(" ").strip('"').strip("\n").strip('"').strip(" ") for x in [inp_root, save_root_vocal, save_root_ins]]
+
+ usable_files = [os.path.join(inp_root, file)
+ for file in os.listdir(inp_root)
+ if file.endswith(tuple(sup_audioext))]
+ try:
+ if paths != None:
+ paths = [path.name for path in paths]
+ else:
+ paths = usable_files
+
+ except:
+ traceback.print_exc()
+ paths = usable_files
+ print(paths)
+ invert=True
+ denoise=True
+ use_custom_parameter=True
+ dim_f=3072
+ dim_t=256
+ n_fft=7680
+ use_custom_compensation=True
+ compensation=1.025
+ suffix = "Vocals_custom" #@param ["Vocals", "Drums", "Bass", "Other"]{allow-input: true}
+ suffix_invert = "Instrumental_custom" #@param ["Instrumental", "Drumless", "Bassless", "Instruments"]{allow-input: true}
+ print_settings = True # @param{type:"boolean"}
+ onnx = id_to_ptm(model_name)
+ compensation = compensation if use_custom_compensation or use_custom_parameter else None
+ mdx_model = prepare_mdx(onnx,use_custom_parameter, dim_f, dim_t, n_fft, compensation=compensation)
+
+
+ for path in paths:
+ #inp_path = os.path.join(inp_root, path)
+ suffix_naming = suffix if use_custom_parameter else None
+ diff_suffix_naming = suffix_invert if use_custom_parameter else None
+ run_mdx(onnx, mdx_model, path, format0, diff=invert,suffix=suffix_naming,diff_suffix=diff_suffix_naming,denoise=denoise)
+
+ if print_settings:
+ print()
+ print('[MDX-Net_Colab settings used]')
+ print(f'Model used: {onnx}')
+ print(f'Model MD5: {mdx.MDX.get_hash(onnx)}')
+ print(f'Model parameters:')
+ print(f' -dim_f: {mdx_model.dim_f}')
+ print(f' -dim_t: {mdx_model.dim_t}')
+ print(f' -n_fft: {mdx_model.n_fft}')
+ print(f' -compensation: {mdx_model.compensation}')
+ print()
+ print('[Input file]')
+ print('filename(s): ')
+ for filename in paths:
+ print(f' -{filename}')
+ infos.append(f"{os.path.basename(filename)}->Success")
+ yield "\n".join(infos)
+ except:
+ infos.append(traceback.format_exc())
+ yield "\n".join(infos)
+ finally:
+ try:
+ del mdx_model
+ except: traceback.print_exc()
+
+ print("clean_empty_cache")
+
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
+
+
+
+
+
+def change_choices():
+ names = [os.path.join(root, file)
+ for root, _, files in os.walk(weight_root)
+ for file in files
+ if file.endswith((".pth", ".onnx"))]
+ indexes_list = [os.path.join(root, name) for root, _, files in os.walk(index_root, topdown=False) for name in files if name.endswith(".index") and "trained" not in name]
+ audio_paths = [os.path.join(audio_root, file) for file in os.listdir(os.path.join(now_dir, "audios"))]
+
+
+ return (
+ {"choices": sorted(names), "__type__": "update"},
+ {"choices": sorted(indexes_list), "__type__": "update"},
+ {"choices": sorted(audio_paths), "__type__": "update"}
+ )
+def change_choices2():
+ names = [os.path.join(root, file)
+ for root, _, files in os.walk(weight_root)
+ for file in files
+ if file.endswith((".pth", ".onnx"))]
+ indexes_list = [os.path.join(root, name) for root, _, files in os.walk(index_root, topdown=False) for name in files if name.endswith(".index") and "trained" not in name]
+
+
+ return (
+ {"choices": sorted(names), "__type__": "update"},
+ {"choices": sorted(indexes_list), "__type__": "update"},
+ )
+def change_choices3():
+
+ audio_paths = [os.path.join(audio_root, file) for file in os.listdir(os.path.join(now_dir, "audios"))]
+ audio_others_paths = [os.path.join(audio_others_root, file) for file in os.listdir(os.path.join(now_dir, "audio-others"))]
+
+
+ return (
+ {"choices": sorted(audio_others_paths), "__type__": "update"},
+ {"choices": sorted(audio_paths), "__type__": "update"}
+ )
+
+def clean():
+ return {"value": "", "__type__": "update"}
+def export_onnx():
+ from infer.modules.onnx.export import export_onnx as eo
+
+ eo()
+
+sr_dict = {
+ "32k": 32000,
+ "40k": 40000,
+ "48k": 48000,
+}
+
+
+def if_done(done, p):
+ while 1:
+ if p.poll() is None:
+ sleep(0.5)
+ else:
+ break
+ done[0] = True
+
+
+def if_done_multi(done, ps):
+ while 1:
+ # poll==None代表进程未结束
+ # 只要有一个进程未结束都不停
+ flag = 1
+ for p in ps:
+ if p.poll() is None:
+ flag = 0
+ sleep(0.5)
+ break
+ if flag == 1:
+ break
+ done[0] = True
+
+def formant_enabled(
+ cbox, qfrency, tmbre, frmntapply, formantpreset, formant_refresh_button
+):
+ if cbox:
+ DoFormant = True
+ CSVutil("csvdb/formanting.csv", "w+", "formanting", DoFormant, qfrency, tmbre)
+
+ # print(f"is checked? - {cbox}\ngot {DoFormant}")
+
+ return (
+ {"value": True, "__type__": "update"},
+ {"visible": True, "__type__": "update"},
+ {"visible": True, "__type__": "update"},
+ {"visible": True, "__type__": "update"},
+ {"visible": True, "__type__": "update"},
+ {"visible": True, "__type__": "update"},
+ )
+
+ else:
+ DoFormant = False
+ CSVutil("csvdb/formanting.csv", "w+", "formanting", DoFormant, qfrency, tmbre)
+
+ # print(f"is checked? - {cbox}\ngot {DoFormant}")
+ return (
+ {"value": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ )
+
+
+def formant_apply(qfrency, tmbre):
+ Quefrency = qfrency
+ Timbre = tmbre
+ DoFormant = True
+ CSVutil("csvdb/formanting.csv", "w+", "formanting", DoFormant, qfrency, tmbre)
+
+ return (
+ {"value": Quefrency, "__type__": "update"},
+ {"value": Timbre, "__type__": "update"},
+ )
+
+def update_fshift_presets(preset, qfrency, tmbre):
+
+ if preset:
+ with open(preset, 'r') as p:
+ content = p.readlines()
+ qfrency, tmbre = content[0].strip(), content[1]
+
+ formant_apply(qfrency, tmbre)
+ else:
+ qfrency, tmbre = preset_apply(preset, qfrency, tmbre)
+
+ return (
+ {"choices": get_fshift_presets(), "__type__": "update"},
+ {"value": qfrency, "__type__": "update"},
+ {"value": tmbre, "__type__": "update"},
+ )
+
+def preprocess_dataset(trainset_dir, exp_dir, sr, n_p):
+ sr = sr_dict[sr]
+ os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True)
+ f = open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "w")
+ f.close()
+ per = 3.0 if config.is_half else 3.7
+ cmd = '"%s" infer/modules/train/preprocess.py "%s" %s %s "%s/logs/%s" %s %.1f' % (
+ config.python_cmd,
+ trainset_dir,
+ sr,
+ n_p,
+ now_dir,
+ exp_dir,
+ config.noparallel,
+ per,
+ )
+ logger.info(cmd)
+ p = Popen(cmd, shell=True) # , stdin=PIPE, stdout=PIPE,stderr=PIPE,cwd=now_dir
+ ###煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
+ done = [False]
+ threading.Thread(
+ target=if_done,
+ args=(
+ done,
+ p,
+ ),
+ ).start()
+ while 1:
+ with open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "r") as f:
+ yield (f.read())
+ sleep(1)
+ if done[0]:
+ break
+ with open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "r") as f:
+ log = f.read()
+ logger.info(log)
+ yield log
+
+
+def extract_f0_feature(gpus, n_p, f0method, if_f0, exp_dir, version19, echl, gpus_rmvpe):
+ gpus = gpus.split("-")
+ os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True)
+ f = open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "w")
+ f.close()
+ if if_f0:
+ if f0method != "rmvpe_gpu":
+ cmd = (
+ '"%s" infer/modules/train/extract/extract_f0_print.py "%s/logs/%s" %s %s'
+ % (
+ config.python_cmd,
+ now_dir,
+ exp_dir,
+ n_p,
+ f0method,
+ echl,
+ )
+ )
+ logger.info(cmd)
+ p = Popen(
+ cmd, shell=True, cwd=now_dir
+ ) # , stdin=PIPE, stdout=PIPE,stderr=PIPE
+ ###煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
+ done = [False]
+ threading.Thread(
+ target=if_done,
+ args=(
+ done,
+ p,
+ ),
+ ).start()
+ else:
+ if gpus_rmvpe != "-":
+ gpus_rmvpe = gpus_rmvpe.split("-")
+ leng = len(gpus_rmvpe)
+ ps = []
+ for idx, n_g in enumerate(gpus_rmvpe):
+ cmd = (
+ '"%s" infer/modules/train/extract/extract_f0_rmvpe.py %s %s %s "%s/logs/%s" %s '
+ % (
+ config.python_cmd,
+ leng,
+ idx,
+ n_g,
+ now_dir,
+ exp_dir,
+ config.is_half,
+ )
+ )
+ logger.info(cmd)
+ p = Popen(
+ cmd, shell=True, cwd=now_dir
+ ) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
+ ps.append(p)
+ ###煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
+ done = [False]
+ threading.Thread(
+ target=if_done_multi, #
+ args=(
+ done,
+ ps,
+ ),
+ ).start()
+ else:
+ cmd = (
+ config.python_cmd
+ + ' infer/modules/train/extract/extract_f0_rmvpe_dml.py "%s/logs/%s" '
+ % (
+ now_dir,
+ exp_dir,
+ )
+ )
+ logger.info(cmd)
+ p = Popen(
+ cmd, shell=True, cwd=now_dir
+ ) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
+ p.wait()
+ done = [True]
+ while 1:
+ with open(
+ "%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r"
+ ) as f:
+ yield (f.read())
+ sleep(1)
+ if done[0]:
+ break
+ with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
+ log = f.read()
+ logger.info(log)
+ yield log
+ ####对不同part分别开多进程
+ """
+ n_part=int(sys.argv[1])
+ i_part=int(sys.argv[2])
+ i_gpu=sys.argv[3]
+ exp_dir=sys.argv[4]
+ os.environ["CUDA_VISIBLE_DEVICES"]=str(i_gpu)
+ """
+ leng = len(gpus)
+ ps = []
+ for idx, n_g in enumerate(gpus):
+ cmd = (
+ '"%s" infer/modules/train/extract_feature_print.py %s %s %s %s "%s/logs/%s" %s'
+ % (
+ config.python_cmd,
+ config.device,
+ leng,
+ idx,
+ n_g,
+ now_dir,
+ exp_dir,
+ version19,
+ )
+ )
+ logger.info(cmd)
+ p = Popen(
+ cmd, shell=True, cwd=now_dir
+ ) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
+ ps.append(p)
+ ###煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
+ done = [False]
+ threading.Thread(
+ target=if_done_multi,
+ args=(
+ done,
+ ps,
+ ),
+ ).start()
+ while 1:
+ with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
+ yield (f.read())
+ sleep(1)
+ if done[0]:
+ break
+ with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
+ log = f.read()
+ logger.info(log)
+ yield log
+
+def get_pretrained_models(path_str, f0_str, sr2):
+ if_pretrained_generator_exist = os.access(
+ "assets/pretrained%s/%sG%s.pth" % (path_str, f0_str, sr2), os.F_OK
+ )
+ if_pretrained_discriminator_exist = os.access(
+ "assets/pretrained%s/%sD%s.pth" % (path_str, f0_str, sr2), os.F_OK
+ )
+ if not if_pretrained_generator_exist:
+ logger.warn(
+ "assets/pretrained%s/%sG%s.pth not exist, will not use pretrained model",
+ path_str,
+ f0_str,
+ sr2,
+ )
+ if not if_pretrained_discriminator_exist:
+ logger.warn(
+ "assets/pretrained%s/%sD%s.pth not exist, will not use pretrained model",
+ path_str,
+ f0_str,
+ sr2,
+ )
+ return (
+ "assets/pretrained%s/%sG%s.pth" % (path_str, f0_str, sr2)
+ if if_pretrained_generator_exist
+ else "",
+ "assets/pretrained%s/%sD%s.pth" % (path_str, f0_str, sr2)
+ if if_pretrained_discriminator_exist
+ else "",
+ )
+
+def change_sr2(sr2, if_f0_3, version19):
+ path_str = "" if version19 == "v1" else "_v2"
+ f0_str = "f0" if if_f0_3 else ""
+ return get_pretrained_models(path_str, f0_str, sr2)
+
+
+def change_version19(sr2, if_f0_3, version19):
+ path_str = "" if version19 == "v1" else "_v2"
+ if sr2 == "32k" and version19 == "v1":
+ sr2 = "40k"
+ to_return_sr2 = (
+ {"choices": ["40k", "48k"], "__type__": "update", "value": sr2}
+ if version19 == "v1"
+ else {"choices": ["40k", "48k", "32k"], "__type__": "update", "value": sr2}
+ )
+ f0_str = "f0" if if_f0_3 else ""
+ return (
+ *get_pretrained_models(path_str, f0_str, sr2),
+ to_return_sr2,
+ )
+
+
+def change_f0(if_f0_3, sr2, version19): # f0method8,pretrained_G14,pretrained_D15
+ path_str = "" if version19 == "v1" else "_v2"
+ return (
+ {"visible": if_f0_3, "__type__": "update"},
+ *get_pretrained_models(path_str, "f0", sr2),
+ )
+
+
+global log_interval
+
+def set_log_interval(exp_dir, batch_size12):
+ log_interval = 1
+ folder_path = os.path.join(exp_dir, "1_16k_wavs")
+
+ if os.path.isdir(folder_path):
+ wav_files_num = len(glob1(folder_path,"*.wav"))
+
+ if wav_files_num > 0:
+ log_interval = math.ceil(wav_files_num / batch_size12)
+ if log_interval > 1:
+ log_interval += 1
+
+ return log_interval
+
+global PID, PROCESS
+
+def click_train(
+ exp_dir1,
+ sr2,
+ if_f0_3,
+ spk_id5,
+ save_epoch10,
+ total_epoch11,
+ batch_size12,
+ if_save_latest13,
+ pretrained_G14,
+ pretrained_D15,
+ gpus16,
+ if_cache_gpu17,
+ if_save_every_weights18,
+ version19,
+):
+ CSVutil("csvdb/stop.csv", "w+", "formanting", False)
+ # 生成filelist
+ exp_dir = "%s/logs/%s" % (now_dir, exp_dir1)
+ os.makedirs(exp_dir, exist_ok=True)
+ gt_wavs_dir = "%s/0_gt_wavs" % (exp_dir)
+ feature_dir = (
+ "%s/3_feature256" % (exp_dir)
+ if version19 == "v1"
+ else "%s/3_feature768" % (exp_dir)
+ )
+ if if_f0_3:
+ f0_dir = "%s/2a_f0" % (exp_dir)
+ f0nsf_dir = "%s/2b-f0nsf" % (exp_dir)
+ names = (
+ set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)])
+ & set([name.split(".")[0] for name in os.listdir(feature_dir)])
+ & set([name.split(".")[0] for name in os.listdir(f0_dir)])
+ & set([name.split(".")[0] for name in os.listdir(f0nsf_dir)])
+ )
+ else:
+ names = set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) & set(
+ [name.split(".")[0] for name in os.listdir(feature_dir)]
+ )
+ opt = []
+ for name in names:
+ if if_f0_3:
+ opt.append(
+ "%s/%s.wav|%s/%s.npy|%s/%s.wav.npy|%s/%s.wav.npy|%s"
+ % (
+ gt_wavs_dir.replace("\\", "\\\\"),
+ name,
+ feature_dir.replace("\\", "\\\\"),
+ name,
+ f0_dir.replace("\\", "\\\\"),
+ name,
+ f0nsf_dir.replace("\\", "\\\\"),
+ name,
+ spk_id5,
+ )
+ )
+ else:
+ opt.append(
+ "%s/%s.wav|%s/%s.npy|%s"
+ % (
+ gt_wavs_dir.replace("\\", "\\\\"),
+ name,
+ feature_dir.replace("\\", "\\\\"),
+ name,
+ spk_id5,
+ )
+ )
+ fea_dim = 256 if version19 == "v1" else 768
+ if if_f0_3:
+ for _ in range(2):
+ opt.append(
+ "%s/logs/mute/0_gt_wavs/mute%s.wav|%s/logs/mute/3_feature%s/mute.npy|%s/logs/mute/2a_f0/mute.wav.npy|%s/logs/mute/2b-f0nsf/mute.wav.npy|%s"
+ % (now_dir, sr2, now_dir, fea_dim, now_dir, now_dir, spk_id5)
+ )
+ else:
+ for _ in range(2):
+ opt.append(
+ "%s/logs/mute/0_gt_wavs/mute%s.wav|%s/logs/mute/3_feature%s/mute.npy|%s"
+ % (now_dir, sr2, now_dir, fea_dim, spk_id5)
+ )
+ shuffle(opt)
+ with open("%s/filelist.txt" % exp_dir, "w") as f:
+ f.write("\n".join(opt))
+ logger.debug("Write filelist done")
+ # 生成config#无需生成config
+ # cmd = python_cmd + " train_nsf_sim_cache_sid_load_pretrain.py -e mi-test -sr 40k -f0 1 -bs 4 -g 0 -te 10 -se 5 -pg pretrained/f0G40k.pth -pd pretrained/f0D40k.pth -l 1 -c 0"
+ logger.info("Use gpus: %s", str(gpus16))
+ if pretrained_G14 == "":
+ logger.info("No pretrained Generator")
+ if pretrained_D15 == "":
+ logger.info("No pretrained Discriminator")
+ if version19 == "v1" or sr2 == "40k":
+ config_path = "v1/%s.json" % sr2
+ else:
+ config_path = "v2/%s.json" % sr2
+ config_save_path = os.path.join(exp_dir, "config.json")
+ if not pathlib.Path(config_save_path).exists():
+ with open(config_save_path, "w", encoding="utf-8") as f:
+ json.dump(
+ config.json_config[config_path],
+ f,
+ ensure_ascii=False,
+ indent=4,
+ sort_keys=True,
+ )
+ f.write("\n")
+ if gpus16:
+ cmd = (
+ '"%s" infer/modules/train/train.py -e "%s" -sr %s -f0 %s -bs %s -g %s -te %s -se %s %s %s -l %s -c %s -sw %s -v %s'
+ % (
+ config.python_cmd,
+ exp_dir1,
+ sr2,
+ 1 if if_f0_3 else 0,
+ batch_size12,
+ gpus16,
+ total_epoch11,
+ save_epoch10,
+ "-pg %s" % pretrained_G14 if pretrained_G14 != "" else "",
+ "-pd %s" % pretrained_D15 if pretrained_D15 != "" else "",
+ 1 if if_save_latest13 == True else 0,
+ 1 if if_cache_gpu17 == True else 0,
+ 1 if if_save_every_weights18 == True else 0,
+ version19,
+ )
+ )
+ else:
+ cmd = (
+ '"%s" infer/modules/train/train.py -e "%s" -sr %s -f0 %s -bs %s -te %s -se %s %s %s -l %s -c %s -sw %s -v %s'
+ % (
+ config.python_cmd,
+ exp_dir1,
+ sr2,
+ 1 if if_f0_3 else 0,
+ batch_size12,
+ total_epoch11,
+ save_epoch10,
+ "-pg %s" % pretrained_G14 if pretrained_G14 != "" else "",
+ "-pd %s" % pretrained_D15 if pretrained_D15 != "" else "",
+ 1 if if_save_latest13 == True else 0,
+ 1 if if_cache_gpu17 == True else 0,
+ 1 if if_save_every_weights18 == True else 0,
+ version19,
+ )
+ )
+ logger.info(cmd)
+ global p
+ p = Popen(cmd, shell=True, cwd=now_dir)
+ global PID
+ PID = p.pid
+
+ p.wait()
+
+ return i18n("Training is done, check train.log"), {"visible": False, "__type__": "update"}, {"visible": True, "__type__": "update"}
+
+
+def train_index(exp_dir1, version19):
+ # exp_dir = "%s/logs/%s" % (now_dir, exp_dir1)
+ exp_dir = "logs/%s" % (exp_dir1)
+ os.makedirs(exp_dir, exist_ok=True)
+ feature_dir = (
+ "%s/3_feature256" % (exp_dir)
+ if version19 == "v1"
+ else "%s/3_feature768" % (exp_dir)
+ )
+ if not os.path.exists(feature_dir):
+ return "请先进行特征提取!"
+ listdir_res = list(os.listdir(feature_dir))
+ if len(listdir_res) == 0:
+ return "请先进行特征提取!"
+ infos = []
+ npys = []
+ for name in sorted(listdir_res):
+ phone = np.load("%s/%s" % (feature_dir, name))
+ npys.append(phone)
+ big_npy = np.concatenate(npys, 0)
+ big_npy_idx = np.arange(big_npy.shape[0])
+ np.random.shuffle(big_npy_idx)
+ big_npy = big_npy[big_npy_idx]
+ if big_npy.shape[0] > 2e5:
+ infos.append("Trying doing kmeans %s shape to 10k centers." % big_npy.shape[0])
+ yield "\n".join(infos)
+ try:
+ big_npy = (
+ MiniBatchKMeans(
+ n_clusters=10000,
+ verbose=True,
+ batch_size=256 * config.n_cpu,
+ compute_labels=False,
+ init="random",
+ )
+ .fit(big_npy)
+ .cluster_centers_
+ )
+ except:
+ info = traceback.format_exc()
+ logger.info(info)
+ infos.append(info)
+ yield "\n".join(infos)
+
+ np.save("%s/total_fea.npy" % exp_dir, big_npy)
+ n_ivf = min(int(16 * np.sqrt(big_npy.shape[0])), big_npy.shape[0] // 39)
+ infos.append("%s,%s" % (big_npy.shape, n_ivf))
+ yield "\n".join(infos)
+ index = faiss.index_factory(256 if version19 == "v1" else 768, "IVF%s,Flat" % n_ivf)
+ # index = faiss.index_factory(256if version19=="v1"else 768, "IVF%s,PQ128x4fs,RFlat"%n_ivf)
+ infos.append("training")
+ yield "\n".join(infos)
+ index_ivf = faiss.extract_index_ivf(index) #
+ index_ivf.nprobe = 1
+ index.train(big_npy)
+ faiss.write_index(
+ index,
+ "%s/trained_IVF%s_Flat_nprobe_%s_%s_%s.index"
+ % (exp_dir, n_ivf, index_ivf.nprobe, exp_dir1, version19),
+ )
+
+ infos.append("adding")
+ yield "\n".join(infos)
+ batch_size_add = 8192
+ for i in range(0, big_npy.shape[0], batch_size_add):
+ index.add(big_npy[i : i + batch_size_add])
+ faiss.write_index(
+ index,
+ "%s/added_IVF%s_Flat_nprobe_%s_%s_%s.index"
+ % (exp_dir, n_ivf, index_ivf.nprobe, exp_dir1, version19),
+ )
+ infos.append(
+ "Successful Index Construction,added_IVF%s_Flat_nprobe_%s_%s_%s.index"
+ % (n_ivf, index_ivf.nprobe, exp_dir1, version19)
+ )
+ # faiss.write_index(index, '%s/added_IVF%s_Flat_FastScan_%s.index'%(exp_dir,n_ivf,version19))
+ # infos.append("成功构建索引,added_IVF%s_Flat_FastScan_%s.index"%(n_ivf,version19))
+ yield "\n".join(infos)
+
+def change_info_(ckpt_path):
+ if not os.path.exists(ckpt_path.replace(os.path.basename(ckpt_path), "train.log")):
+ return {"__type__": "update"}, {"__type__": "update"}, {"__type__": "update"}
+ try:
+ with open(
+ ckpt_path.replace(os.path.basename(ckpt_path), "train.log"), "r"
+ ) as f:
+ info = eval(f.read().strip("\n").split("\n")[0].split("\t")[-1])
+ sr, f0 = info["sample_rate"], info["if_f0"]
+ version = "v2" if ("version" in info and info["version"] == "v2") else "v1"
+ return sr, str(f0), version
+ except:
+ traceback.print_exc()
+ return {"__type__": "update"}, {"__type__": "update"}, {"__type__": "update"}
+
+F0GPUVisible = config.dml == False
+
+
+def change_f0_method(f0method8):
+ if f0method8 == "rmvpe_gpu":
+ visible = F0GPUVisible
+ else:
+ visible = False
+ return {"visible": visible, "__type__": "update"}
+
+
+
+def export_onnx(model_path, exported_path):
+ device = torch.device("cpu")
+ checkpoint = torch.load(model_path, map_location=device)
+ vec_channels = 256 if checkpoint.get("version", "v1") == "v1" else 768
+
+ test_inputs = {
+ "phone": torch.rand(1, 200, vec_channels),
+ "phone_lengths": torch.LongTensor([200]),
+ "pitch": torch.randint(5, 255, (1, 200)),
+ "pitchf": torch.rand(1, 200),
+ "ds": torch.zeros(1).long(),
+ "rnd": torch.rand(1, 192, 200)
+ }
+
+ checkpoint["config"][-3] = checkpoint["weight"]["emb_g.weight"].shape[0]
+ net_g = SynthesizerTrnMsNSFsidM(*checkpoint["config"], is_half=False, version=checkpoint.get("version", "v1"))
+
+ net_g.load_state_dict(checkpoint["weight"], strict=False)
+ net_g = net_g.to(device)
+
+ dynamic_axes = {"phone": [1], "pitch": [1], "pitchf": [1], "rnd": [2]}
+
+ torch.onnx.export(
+ net_g,
+ tuple(value.to(device) for value in test_inputs.values()),
+ exported_path,
+ dynamic_axes=dynamic_axes,
+ do_constant_folding=False,
+ opset_version=13,
+ verbose=False,
+ input_names=list(test_inputs.keys()),
+ output_names=["audio"],
+ )
+ return "Finished"
+
+
+
+import re as regex
+import scipy.io.wavfile as wavfile
+
+cli_current_page = "HOME"
+
+
+def cli_split_command(com):
+ exp = r'(?:(?<=\s)|^)"(.*?)"(?=\s|$)|(\S+)'
+ split_array = regex.findall(exp, com)
+ split_array = [group[0] if group[0] else group[1] for group in split_array]
+ return split_array
+
+
+def execute_generator_function(genObject):
+ for _ in genObject:
+ pass
+
+
+def cli_infer(com):
+ # get VC first
+ com = cli_split_command(com)
+ model_name = com[0]
+ source_audio_path = com[1]
+ output_file_name = com[2]
+ feature_index_path = com[3]
+ f0_file = None # Not Implemented Yet
+
+ # Get parameters for inference
+ speaker_id = int(com[4])
+ transposition = float(com[5])
+ f0_method = com[6]
+ crepe_hop_length = int(com[7])
+ harvest_median_filter = int(com[8])
+ resample = int(com[9])
+ mix = float(com[10])
+ feature_ratio = float(com[11])
+ protection_amnt = float(com[12])
+ protect1 = 0.5
+
+ if com[14] == "False" or com[14] == "false":
+ DoFormant = False
+ Quefrency = 0.0
+ Timbre = 0.0
+ CSVutil(
+ "csvdb/formanting.csv", "w+", "formanting", DoFormant, Quefrency, Timbre
+ )
+
+ else:
+ DoFormant = True
+ Quefrency = float(com[15])
+ Timbre = float(com[16])
+ CSVutil(
+ "csvdb/formanting.csv", "w+", "formanting", DoFormant, Quefrency, Timbre
+ )
+
+ print("Mangio-RVC-Fork Infer-CLI: Starting the inference...")
+ vc_data = vc.get_vc(model_name, protection_amnt, protect1)
+ print(vc_data)
+ print("Mangio-RVC-Fork Infer-CLI: Performing inference...")
+ conversion_data = vc.vc_single(
+ speaker_id,
+ source_audio_path,
+ source_audio_path,
+ transposition,
+ f0_file,
+ f0_method,
+ feature_index_path,
+ feature_index_path,
+ feature_ratio,
+ harvest_median_filter,
+ resample,
+ mix,
+ protection_amnt,
+ crepe_hop_length,
+ )
+ if "Success." in conversion_data[0]:
+ print(
+ "Mangio-RVC-Fork Infer-CLI: Inference succeeded. Writing to %s/%s..."
+ % ("audio-outputs", output_file_name)
+ )
+ wavfile.write(
+ "%s/%s" % ("audio-outputs", output_file_name),
+ conversion_data[1][0],
+ conversion_data[1][1],
+ )
+ print(
+ "Mangio-RVC-Fork Infer-CLI: Finished! Saved output to %s/%s"
+ % ("audio-outputs", output_file_name)
+ )
+ else:
+ print("Mangio-RVC-Fork Infer-CLI: Inference failed. Here's the traceback: ")
+ print(conversion_data[0])
+
+
+def cli_pre_process(com):
+ com = cli_split_command(com)
+ model_name = com[0]
+ trainset_directory = com[1]
+ sample_rate = com[2]
+ num_processes = int(com[3])
+
+ print("Mangio-RVC-Fork Pre-process: Starting...")
+ generator = preprocess_dataset(
+ trainset_directory, model_name, sample_rate, num_processes
+ )
+ execute_generator_function(generator)
+ print("Mangio-RVC-Fork Pre-process: Finished")
+
+
+def cli_extract_feature(com):
+ com = cli_split_command(com)
+ model_name = com[0]
+ gpus = com[1]
+ num_processes = int(com[2])
+ has_pitch_guidance = True if (int(com[3]) == 1) else False
+ f0_method = com[4]
+ crepe_hop_length = int(com[5])
+ version = com[6] # v1 or v2
+
+ print("Mangio-RVC-CLI: Extract Feature Has Pitch: " + str(has_pitch_guidance))
+ print("Mangio-RVC-CLI: Extract Feature Version: " + str(version))
+ print("Mangio-RVC-Fork Feature Extraction: Starting...")
+ generator = extract_f0_feature(
+ gpus,
+ num_processes,
+ f0_method,
+ has_pitch_guidance,
+ model_name,
+ version,
+ crepe_hop_length,
+ )
+ execute_generator_function(generator)
+ print("Mangio-RVC-Fork Feature Extraction: Finished")
+
+
+def cli_train(com):
+ com = cli_split_command(com)
+ model_name = com[0]
+ sample_rate = com[1]
+ has_pitch_guidance = True if (int(com[2]) == 1) else False
+ speaker_id = int(com[3])
+ save_epoch_iteration = int(com[4])
+ total_epoch = int(com[5]) # 10000
+ batch_size = int(com[6])
+ gpu_card_slot_numbers = com[7]
+ if_save_latest = True if (int(com[8]) == 1) else False
+ if_cache_gpu = True if (int(com[9]) == 1) else False
+ if_save_every_weight = True if (int(com[10]) == 1) else False
+ version = com[11]
+
+ pretrained_base = "pretrained/" if version == "v1" else "pretrained_v2/"
+
+ g_pretrained_path = "%sf0G%s.pth" % (pretrained_base, sample_rate)
+ d_pretrained_path = "%sf0D%s.pth" % (pretrained_base, sample_rate)
+
+ print("Mangio-RVC-Fork Train-CLI: Training...")
+ click_train(
+ model_name,
+ sample_rate,
+ has_pitch_guidance,
+ speaker_id,
+ save_epoch_iteration,
+ total_epoch,
+ batch_size,
+ if_save_latest,
+ g_pretrained_path,
+ d_pretrained_path,
+ gpu_card_slot_numbers,
+ if_cache_gpu,
+ if_save_every_weight,
+ version,
+ )
+
+
+def cli_train_feature(com):
+ com = cli_split_command(com)
+ model_name = com[0]
+ version = com[1]
+ print("Mangio-RVC-Fork Train Feature Index-CLI: Training... Please wait")
+ generator = train_index(model_name, version)
+ execute_generator_function(generator)
+ print("Mangio-RVC-Fork Train Feature Index-CLI: Done!")
+
+
+def cli_extract_model(com):
+ com = cli_split_command(com)
+ model_path = com[0]
+ save_name = com[1]
+ sample_rate = com[2]
+ has_pitch_guidance = com[3]
+ info = com[4]
+ version = com[5]
+ extract_small_model_process = extract_small_model(
+ model_path, save_name, sample_rate, has_pitch_guidance, info, version
+ )
+ if extract_small_model_process == "Success.":
+ print("Mangio-RVC-Fork Extract Small Model: Success!")
+ else:
+ print(str(extract_small_model_process))
+ print("Mangio-RVC-Fork Extract Small Model: Failed!")
+
+
+def preset_apply(preset, qfer, tmbr):
+ if str(preset) != "":
+ with open(str(preset), "r") as p:
+ content = p.readlines()
+ qfer, tmbr = content[0].split("\n")[0], content[1]
+ formant_apply(qfer, tmbr)
+ else:
+ pass
+ return (
+ {"value": qfer, "__type__": "update"},
+ {"value": tmbr, "__type__": "update"},
+ )
+
+
+def print_page_details():
+ if cli_current_page == "HOME":
+ print(
+ "\n go home : Takes you back to home with a navigation list."
+ "\n go infer : Takes you to inference command execution."
+ "\n go pre-process : Takes you to training step.1) pre-process command execution."
+ "\n go extract-feature : Takes you to training step.2) extract-feature command execution."
+ "\n go train : Takes you to training step.3) being or continue training command execution."
+ "\n go train-feature : Takes you to the train feature index command execution."
+ "\n go extract-model : Takes you to the extract small model command execution."
+ )
+ elif cli_current_page == "INFER":
+ print(
+ "\n arg 1) model name with .pth in ./weights: mi-test.pth"
+ "\n arg 2) source audio path: myFolder\\MySource.wav"
+ "\n arg 3) output file name to be placed in './audio-outputs': MyTest.wav"
+ "\n arg 4) feature index file path: logs/mi-test/added_IVF3042_Flat_nprobe_1.index"
+ "\n arg 5) speaker id: 0"
+ "\n arg 6) transposition: 0"
+ "\n arg 7) f0 method: harvest (pm, harvest, crepe, crepe-tiny, hybrid[x,x,x,x], mangio-crepe, mangio-crepe-tiny, rmvpe)"
+ "\n arg 8) crepe hop length: 160"
+ "\n arg 9) harvest median filter radius: 3 (0-7)"
+ "\n arg 10) post resample rate: 0"
+ "\n arg 11) mix volume envelope: 1"
+ "\n arg 12) feature index ratio: 0.78 (0-1)"
+ "\n arg 13) Voiceless Consonant Protection (Less Artifact): 0.33 (Smaller number = more protection. 0.50 means Dont Use.)"
+ "\n arg 14) Whether to formant shift the inference audio before conversion: False (if set to false, you can ignore setting the quefrency and timbre values for formanting)"
+ "\n arg 15)* Quefrency for formanting: 8.0 (no need to set if arg14 is False/false)"
+ "\n arg 16)* Timbre for formanting: 1.2 (no need to set if arg14 is False/false) \n"
+ "\nExample: mi-test.pth saudio/Sidney.wav myTest.wav logs/mi-test/added_index.index 0 -2 harvest 160 3 0 1 0.95 0.33 0.45 True 8.0 1.2"
+ )
+ elif cli_current_page == "PRE-PROCESS":
+ print(
+ "\n arg 1) Model folder name in ./logs: mi-test"
+ "\n arg 2) Trainset directory: mydataset (or) E:\\my-data-set"
+ "\n arg 3) Sample rate: 40k (32k, 40k, 48k)"
+ "\n arg 4) Number of CPU threads to use: 8 \n"
+ "\nExample: mi-test mydataset 40k 24"
+ )
+ elif cli_current_page == "EXTRACT-FEATURE":
+ print(
+ "\n arg 1) Model folder name in ./logs: mi-test"
+ "\n arg 2) Gpu card slot: 0 (0-1-2 if using 3 GPUs)"
+ "\n arg 3) Number of CPU threads to use: 8"
+ "\n arg 4) Has Pitch Guidance?: 1 (0 for no, 1 for yes)"
+ "\n arg 5) f0 Method: harvest (pm, harvest, dio, crepe)"
+ "\n arg 6) Crepe hop length: 128"
+ "\n arg 7) Version for pre-trained models: v2 (use either v1 or v2)\n"
+ "\nExample: mi-test 0 24 1 harvest 128 v2"
+ )
+ elif cli_current_page == "TRAIN":
+ print(
+ "\n arg 1) Model folder name in ./logs: mi-test"
+ "\n arg 2) Sample rate: 40k (32k, 40k, 48k)"
+ "\n arg 3) Has Pitch Guidance?: 1 (0 for no, 1 for yes)"
+ "\n arg 4) speaker id: 0"
+ "\n arg 5) Save epoch iteration: 50"
+ "\n arg 6) Total epochs: 10000"
+ "\n arg 7) Batch size: 8"
+ "\n arg 8) Gpu card slot: 0 (0-1-2 if using 3 GPUs)"
+ "\n arg 9) Save only the latest checkpoint: 0 (0 for no, 1 for yes)"
+ "\n arg 10) Whether to cache training set to vram: 0 (0 for no, 1 for yes)"
+ "\n arg 11) Save extracted small model every generation?: 0 (0 for no, 1 for yes)"
+ "\n arg 12) Model architecture version: v2 (use either v1 or v2)\n"
+ "\nExample: mi-test 40k 1 0 50 10000 8 0 0 0 0 v2"
+ )
+ elif cli_current_page == "TRAIN-FEATURE":
+ print(
+ "\n arg 1) Model folder name in ./logs: mi-test"
+ "\n arg 2) Model architecture version: v2 (use either v1 or v2)\n"
+ "\nExample: mi-test v2"
+ )
+ elif cli_current_page == "EXTRACT-MODEL":
+ print(
+ "\n arg 1) Model Path: logs/mi-test/G_168000.pth"
+ "\n arg 2) Model save name: MyModel"
+ "\n arg 3) Sample rate: 40k (32k, 40k, 48k)"
+ "\n arg 4) Has Pitch Guidance?: 1 (0 for no, 1 for yes)"
+ '\n arg 5) Model information: "My Model"'
+ "\n arg 6) Model architecture version: v2 (use either v1 or v2)\n"
+ '\nExample: logs/mi-test/G_168000.pth MyModel 40k 1 "Created by Cole Mangio" v2'
+ )
+
+def change_page(page):
+ global cli_current_page
+ cli_current_page = page
+ return 0
+
+def execute_command(com):
+ if com == "go home":
+ return change_page("HOME")
+ elif com == "go infer":
+ return change_page("INFER")
+ elif com == "go pre-process":
+ return change_page("PRE-PROCESS")
+ elif com == "go extract-feature":
+ return change_page("EXTRACT-FEATURE")
+ elif com == "go train":
+ return change_page("TRAIN")
+ elif com == "go train-feature":
+ return change_page("TRAIN-FEATURE")
+ elif com == "go extract-model":
+ return change_page("EXTRACT-MODEL")
+ else:
+ if com[:3] == "go ":
+ print("page '%s' does not exist!" % com[3:])
+ return 0
+
+ if cli_current_page == "INFER":
+ cli_infer(com)
+ elif cli_current_page == "PRE-PROCESS":
+ cli_pre_process(com)
+ elif cli_current_page == "EXTRACT-FEATURE":
+ cli_extract_feature(com)
+ elif cli_current_page == "TRAIN":
+ cli_train(com)
+ elif cli_current_page == "TRAIN-FEATURE":
+ cli_train_feature(com)
+ elif cli_current_page == "EXTRACT-MODEL":
+ cli_extract_model(com)
+
+def cli_navigation_loop():
+ while True:
+ print("\nYou are currently in '%s':" % cli_current_page)
+ print_page_details()
+ command = input("%s: " % cli_current_page)
+ try:
+ execute_command(command)
+ except:
+ print(traceback.format_exc())
+
+
+if config.is_cli:
+ print("\n\nMangio-RVC-Fork v2 CLI App!\n")
+ print(
+ "Welcome to the CLI version of RVC. Please read the documentation on https://github.com/Mangio621/Mangio-RVC-Fork (README.MD) to understand how to use this app.\n"
+ )
+ cli_navigation_loop()
+
+
+
+
+
+def switch_pitch_controls(f0method0):
+ is_visible = f0method0 != 'rmvpe'
+
+ if rvc_globals.NotesOrHertz:
+ return (
+ {"visible": False, "__type__": "update"},
+ {"visible": is_visible, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": is_visible, "__type__": "update"}
+ )
+ else:
+ return (
+ {"visible": is_visible, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": is_visible, "__type__": "update"},
+ {"visible": False, "__type__": "update"}
+ )
+
+def match_index(sid0):
+ picked = False
+ # folder = sid0.split('.')[0]
+
+ # folder = re.split(r'. |_', sid0)[0]
+ folder = sid0.split(".")[0].split("_")[0]
+ # folder_test = sid0.split('.')[0].split('_')[0].split('-')[0]
+ parent_dir = "./logs/" + folder
+ # print(parent_dir)
+ if os.path.exists(parent_dir):
+ # print('path exists')
+ for filename in os.listdir(parent_dir.replace("\\", "/")):
+ if filename.endswith(".index"):
+ for i in range(len(indexes_list)):
+ if indexes_list[i] == (
+ os.path.join(("./logs/" + folder), filename).replace("\\", "/")
+ ):
+ # print('regular index found')
+ break
+ else:
+ if indexes_list[i] == (
+ os.path.join(
+ ("./logs/" + folder.lower()), filename
+ ).replace("\\", "/")
+ ):
+ # print('lowered index found')
+ parent_dir = "./logs/" + folder.lower()
+ break
+ # elif (indexes_list[i]).casefold() == ((os.path.join(("./logs/" + folder), filename).replace('\\','/')).casefold()):
+ # print('8')
+ # parent_dir = "./logs/" + folder.casefold()
+ # break
+ # elif (indexes_list[i]) == ((os.path.join(("./logs/" + folder_test), filename).replace('\\','/'))):
+ # parent_dir = "./logs/" + folder_test
+ # print(parent_dir)
+ # break
+ # elif (indexes_list[i]) == (os.path.join(("./logs/" + folder_test.lower()), filename).replace('\\','/')):
+ # parent_dir = "./logs/" + folder_test
+ # print(parent_dir)
+ # break
+ # else:
+ # #print('couldnt find index')
+ # continue
+
+ # print('all done')
+ index_path = os.path.join(
+ parent_dir.replace("\\", "/"), filename.replace("\\", "/")
+ ).replace("\\", "/")
+ # print(index_path)
+ return (index_path, index_path)
+
+ else:
+ # print('nothing found')
+ return ("", "")
+
+def stoptraining(mim):
+ if int(mim) == 1:
+ CSVutil("csvdb/stop.csv", "w+", "stop", "True")
+ # p.terminate()
+ # p.kill()
+ try:
+ os.kill(PID, signal.SIGTERM)
+ except Exception as e:
+ print(f"Couldn't click due to {e}")
+ pass
+ else:
+ pass
+
+ return (
+ {"visible": False, "__type__": "update"},
+ {"visible": True, "__type__": "update"},
+ )
+
+weights_dir = 'weights/'
+
+def note_to_hz(note_name):
+ SEMITONES = {'C': -9, 'C#': -8, 'D': -7, 'D#': -6, 'E': -5, 'F': -4, 'F#': -3, 'G': -2, 'G#': -1, 'A': 0, 'A#': 1, 'B': 2}
+ pitch_class, octave = note_name[:-1], int(note_name[-1])
+ semitone = SEMITONES[pitch_class]
+ note_number = 12 * (octave - 4) + semitone
+ frequency = 440.0 * (2.0 ** (1.0/12)) ** note_number
+ return frequency
+
+def save_to_wav(record_button):
+ if record_button is None:
+ pass
+ else:
+ path_to_file=record_button
+ new_name = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")+'.wav'
+ new_path='./audios/'+new_name
+ shutil.move(path_to_file,new_path)
+ return new_name
+def save_to_wav2_edited(dropbox):
+ if dropbox is None:
+ pass
+ else:
+ file_path = dropbox.name
+ target_path = os.path.join('audios', os.path.basename(file_path))
+
+ if os.path.exists(target_path):
+ os.remove(target_path)
+ print('Replacing old dropdown file...')
+
+ shutil.move(file_path, target_path)
+ return
+def save_to_wav2(dropbox):
+ file_path = dropbox.name
+ target_path = os.path.join('audios', os.path.basename(file_path))
+
+ if os.path.exists(target_path):
+ os.remove(target_path)
+ print('Replacing old dropdown file...')
+
+ shutil.move(file_path, target_path)
+ return target_path
+
+from gtts import gTTS
+import edge_tts
+import asyncio
+
+
+
+
+def custom_voice(
+ _values, # filter indices
+ audio_files, # all audio files
+ model_voice_path='',
+ transpose=0,
+ f0method='pm',
+ index_rate_=float(0.66),
+ crepe_hop_length_=float(64),
+ f0_autotune=False,
+ file_index='',
+ file_index2='',
+ ):
+
+ vc.get_vc(model_voice_path)
+
+
+ for _value_item in _values:
+ filename = "audio2/"+audio_files[_value_item] if _value_item != "converted_tts" else audio_files[0]
+ #filename = "audio2/"+audio_files[_value_item]
+ try:
+ print(audio_files[_value_item], model_voice_path)
+ except:
+ pass
+ info_, (sample_, audio_output_) = vc.vc_single_dont_save(
+ sid=0,
+ input_audio_path0=filename, #f"audio2/{filename}",
+ input_audio_path1=filename, #f"audio2/{filename}",
+ f0_up_key=transpose, # transpose for m to f and reverse 0 12
+ f0_file=None,
+ f0_method= f0method,
+ file_index= file_index, # dir pwd?
+ file_index2= file_index2,
+ # file_big_npy1,
+ index_rate= index_rate_,
+ filter_radius= int(3),
+ resample_sr= int(0),
+ rms_mix_rate= float(0.25),
+ protect= float(0.33),
+ crepe_hop_length= crepe_hop_length_,
+ f0_autotune=f0_autotune,
+ f0_min=50,
+ note_min=50,
+ f0_max=1100,
+ note_max=1100
+ )
+
+ sf.write(
+ file= filename, #f"audio2/{filename}",
+ samplerate=sample_,
+ data=audio_output_
+ )
+def cast_to_device(tensor, device):
+ try:
+ return tensor.to(device)
+ except Exception as e:
+ print(e)
+ return tensor
+
+
+def __bark__(text, voice_preset):
+ os.makedirs(os.path.join(now_dir,"tts"), exist_ok=True)
+ from transformers import AutoProcessor, BarkModel
+ device = "cuda:0" if torch.cuda.is_available() else "cpu"
+ dtype = torch.float32 if "cpu" in device else torch.float16
+ bark_processor = AutoProcessor.from_pretrained(
+ "suno/bark",
+ cache_dir=os.path.join(now_dir,"tts","suno/bark"),
+ torch_dtype=dtype)
+ bark_model = BarkModel.from_pretrained(
+ "suno/bark",
+ cache_dir=os.path.join(now_dir,"tts","suno/bark"),
+ torch_dtype=dtype).to(device)
+ # bark_model.enable_cpu_offload()
+ inputs = bark_processor(
+ text=[text],
+ return_tensors="pt",
+ voice_preset=voice_preset
+ )
+ tensor_dict = {k: cast_to_device(v,device) if hasattr(v,"to") else v for k, v in inputs.items()}
+ speech_values = bark_model.generate(**tensor_dict, do_sample=True)
+ sampling_rate = bark_model.generation_config.sample_rate
+ speech = speech_values.cpu().numpy().squeeze()
+ return speech, sampling_rate
+
+
+
+def make_test(
+ tts_text,
+ tts_voice,
+ model_path,
+ index_path,
+ transpose,
+ f0_method,
+ index_rate,
+ crepe_hop_length,
+ f0_autotune,
+ tts_method
+ ):
+
+ if tts_voice == None:
+ return
+
+ filename = os.path.join(now_dir, "audio-outputs", "converted_tts.wav")
+ if "SET_LIMIT" == os.getenv("DEMO"):
+ if len(tts_text) > 60:
+ tts_text = tts_text[:60]
+ print("DEMO; limit to 60 characters")
+
+ language = tts_voice[:2]
+ if tts_method == "Edge-tts":
+ try:
+ #nest_asyncio.apply() # gradio;not
+ asyncio.run(edge_tts.Communicate(tts_text, "-".join(tts_voice.split('-')[:-1])).save(filename))
+ except:
+ try:
+ tts = gTTS(tts_text, lang=language)
+ tts.save(filename)
+ tts.save
+ print(f'No audio was received. Please change the tts voice for {tts_voice}. USING gTTS.')
+ except:
+ tts = gTTS('a', lang=language)
+ tts.save(filename)
+ print('Error: Audio will be replaced.')
+
+ os.system("cp audio-outputs/converted_tts.wav audio-outputs/real_tts.wav")
+
+ custom_voice(
+ ["converted_tts"], # filter indices
+ ["audio-outputs/converted_tts.wav"], # all audio files
+ model_voice_path=model_path,
+ transpose=transpose,
+ f0method=f0_method,
+ index_rate_=index_rate,
+ crepe_hop_length_=crepe_hop_length,
+ f0_autotune=f0_autotune,
+ file_index='',
+ file_index2=index_path,
+ )
+ return os.path.join(now_dir, "audio-outputs", "converted_tts.wav"), os.path.join(now_dir, "audio-outputs", "real_tts.wav")
+ elif tts_method == "Bark-tts":
+ try:
+
+ script = tts_text.replace("\n", " ").strip()
+ sentences = sent_tokenize(script)
+ print(sentences)
+ silence = np.zeros(int(0.25 * SAMPLE_RATE))
+ pieces = []
+ nombre_archivo = os.path.join(now_dir, "audio-outputs", "bark_out.wav")
+ for sentence in sentences:
+ audio_array , _ = __bark__(sentence, tts_voice.split("-")[0])
+ pieces += [audio_array, silence.copy()]
+
+ sf.write(
+ file= nombre_archivo,
+ samplerate=SAMPLE_RATE,
+ data=np.concatenate(pieces)
+ )
+ vc.get_vc(model_path)
+ info_, (sample_, audio_output_) = vc.vc_single_dont_save(
+ sid=0,
+ input_audio_path0=os.path.join(now_dir, "audio-outputs", "bark_out.wav"), #f"audio2/{filename}",
+ input_audio_path1=os.path.join(now_dir, "audio-outputs", "bark_out.wav"), #f"audio2/{filename}",
+ f0_up_key=transpose, # transpose for m to f and reverse 0 12
+ f0_file=None,
+ f0_method=f0_method,
+ file_index= '', # dir pwd?
+ file_index2= index_path,
+ # file_big_npy1,
+ index_rate= index_rate,
+ filter_radius= int(3),
+ resample_sr= int(0),
+ rms_mix_rate= float(0.25),
+ protect= float(0.33),
+ crepe_hop_length= crepe_hop_length,
+ f0_autotune=f0_autotune,
+ f0_min=50,
+ note_min=50,
+ f0_max=1100,
+ note_max=1100
+ )
+ wavfile.write(os.path.join(now_dir, "audio-outputs", "converted_bark.wav"), rate=sample_, data=audio_output_)
+ return os.path.join(now_dir, "audio-outputs", "converted_bark.wav"), nombre_archivo
+
+ except Exception as e:
+ print(f"{e}")
+ return None, None
+
+
+
+
+
+
+def GradioSetup(UTheme=gr.themes.Soft()):
+
+ default_weight = names[0] if names else ''
+
+ with gr.Blocks(theme='JohnSmith9982/small_and_pretty', title="Applio") as app:
+ gr.Markdown("🍏 Applio (Mangio-RVC-Fork HF)")
+ gr.Markdown("More spaces: [Aesthetic_RVC_Inference_HF](https://huggingface.co/spaces/r3gm/Aesthetic_RVC_Inference_HF), [AICoverGen](https://huggingface.co/spaces/r3gm/AICoverGen), [Ultimate-Vocal-Remover-WebUI](https://huggingface.co/spaces/r3gm/Ultimate-Vocal-Remover-WebUI), [Advanced-RVC-Inference](https://huggingface.co/spaces/r3gm/Advanced-RVC-Inference)")
+ gr.HTML(" The current space only uses CPU, so it's only for inference. If you have issues with the queue, I recommend duplicating the space.
")
+ gr.Markdown(
+ "[![Duplicate this Space](https://huggingface.co/datasets/huggingface/badges/raw/main/duplicate-this-space-sm-dark.svg)](https://huggingface.co/spaces/r3gm/RVC_HF?duplicate=true)\n\n"
+ )
+ with gr.Tabs():
+ with gr.TabItem(i18n("Model Inference")):
+ with gr.Row():
+ sid0 = gr.Dropdown(label=i18n("Inferencing voice:"), choices=sorted(names), value=default_weight)
+ refresh_button = gr.Button(i18n("Refresh"), variant="primary")
+ clean_button = gr.Button(i18n("Unload voice to save GPU memory"), variant="primary")
+ clean_button.click(fn=lambda: ({"value": "", "__type__": "update"}), inputs=[], outputs=[sid0])
+
+
+ with gr.TabItem(i18n("Single")):
+ with gr.Row():
+ spk_item = gr.Slider(
+ minimum=0,
+ maximum=2333,
+ step=1,
+ label=i18n("Select Speaker/Singer ID:"),
+ value=0,
+ visible=False,
+ interactive=True,
+ )
+
+
+ with gr.Group():
+ with gr.Row():
+ with gr.Column(): # First column for audio-related inputs
+ dropbox = gr.File(label=i18n("Drag your audio here:"))
+ record_button=gr.Audio(source="microphone", label=i18n("Or record an audio:"), type="filepath")
+ input_audio0 = gr.Textbox(
+ label=i18n("Manual path to the audio file to be processed"),
+ value=os.path.join(now_dir, "audios", "someguy.mp3"),
+ visible=False
+ )
+ input_audio1 = gr.Dropdown(
+ label=i18n("Auto detect audio path and select from the dropdown:"),
+ choices=sorted(audio_paths),
+ value='',
+ interactive=True,
+ )
+
+ input_audio1.select(fn=lambda:'',inputs=[],outputs=[input_audio0])
+ input_audio0.input(fn=lambda:'',inputs=[],outputs=[input_audio1])
+
+ dropbox.upload(fn=save_to_wav2, inputs=[dropbox], outputs=[input_audio0])
+ dropbox.upload(fn=easy_infer.change_choices2, inputs=[], outputs=[input_audio1])
+ record_button.change(fn=save_to_wav, inputs=[record_button], outputs=[input_audio0])
+ record_button.change(fn=easy_infer.change_choices2, inputs=[], outputs=[input_audio1])
+
+ best_match_index_path1 = match_index(sid0.value) # Get initial index from default sid0 (first voice model in list)
+
+ with gr.Column(): # Second column for pitch shift and other options
+ file_index2 = gr.Dropdown(
+ label=i18n("Auto-detect index path and select from the dropdown:"),
+ choices=get_indexes(),
+ value=best_match_index_path1,
+ interactive=True,
+ allow_custom_value=True,
+ )
+ index_rate1 = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label=i18n("Search feature ratio:"),
+ value=0.75,
+ interactive=True,
+ )
+ refresh_button.click(
+ fn=change_choices, inputs=[], outputs=[sid0, file_index2, input_audio1]
+ )
+ with gr.Column():
+ vc_transform0 = gr.Number(
+ label=i18n("Transpose (integer, number of semitones, raise by an octave: 12, lower by an octave: -12):"), value=0
+ )
+
+ # Create a checkbox for advanced settings
+ advanced_settings_checkbox = gr.Checkbox(
+ value=False,
+ label=i18n("Advanced Settings"),
+ interactive=True,
+ )
+
+ # Advanced settings container
+ with gr.Column(visible=False) as advanced_settings: # Initially hidden
+ with gr.Row(label = i18n("Advanced Settings"), open = False):
+ with gr.Column():
+ f0method0 = gr.Radio(
+ label=i18n(
+ "Select the pitch extraction algorithm:"
+ ),
+ choices=["pm", "harvest", "dio", "crepe", "crepe-tiny", "mangio-crepe", "mangio-crepe-tiny", "rmvpe", "rmvpe+"],
+ value="rmvpe+",
+ interactive=True,
+ )
+ f0_autotune = gr.Checkbox(
+ label="Enable autotune",
+ interactive=True
+ )
+ crepe_hop_length = gr.Slider(
+ minimum=1,
+ maximum=512,
+ step=1,
+ label=i18n("Mangio-Crepe Hop Length (Only applies to mangio-crepe): Hop length refers to the time it takes for the speaker to jump to a dramatic pitch. Lower hop lengths take more time to infer but are more pitch accurate."),
+ value=120,
+ interactive=True,
+ visible=False,
+ )
+ filter_radius0 = gr.Slider(
+ minimum=0,
+ maximum=7,
+ label=i18n("If >=3: apply median filtering to the harvested pitch results. The value represents the filter radius and can reduce breathiness."),
+ value=3,
+ step=1,
+ interactive=True,
+ )
+
+ minpitch_slider = gr.Slider(
+ label = i18n("Min pitch:"),
+ info = i18n("Specify minimal pitch for inference [HZ]"),
+ step = 0.1,
+ minimum = 1,
+ scale = 0,
+ value = 50,
+ maximum = 16000,
+ interactive = True,
+ visible = (not rvc_globals.NotesOrHertz) and (f0method0.value != 'rmvpe'),
+ )
+ minpitch_txtbox = gr.Textbox(
+ label = i18n("Min pitch:"),
+ info = i18n("Specify minimal pitch for inference [NOTE][OCTAVE]"),
+ placeholder = "C5",
+ visible = (rvc_globals.NotesOrHertz) and (f0method0.value != 'rmvpe'),
+ interactive = True,
+ )
+
+ maxpitch_slider = gr.Slider(
+ label = i18n("Max pitch:"),
+ info = i18n("Specify max pitch for inference [HZ]"),
+ step = 0.1,
+ minimum = 1,
+ scale = 0,
+ value = 1100,
+ maximum = 16000,
+ interactive = True,
+ visible = (not rvc_globals.NotesOrHertz) and (f0method0.value != 'rmvpe'),
+ )
+ maxpitch_txtbox = gr.Textbox(
+ label = i18n("Max pitch:"),
+ info = i18n("Specify max pitch for inference [NOTE][OCTAVE]"),
+ placeholder = "C6",
+ visible = (rvc_globals.NotesOrHertz) and (f0method0.value != 'rmvpe'),
+ interactive = True,
+ )
+
+ with gr.Column():
+ file_index1 = gr.Textbox(
+ label=i18n("Feature search database file path:"),
+ value="",
+ interactive=True,
+ )
+
+ with gr.Accordion(label = i18n("Custom f0 [Root pitch] File"), open = False):
+ f0_file = gr.File(label=i18n("F0 curve file (optional). One pitch per line. Replaces the default F0 and pitch modulation:"))
+
+ f0method0.change(
+ fn=lambda radio: (
+ {
+ "visible": radio in ['mangio-crepe', 'mangio-crepe-tiny'],
+ "__type__": "update"
+ }
+ ),
+ inputs=[f0method0],
+ outputs=[crepe_hop_length]
+ )
+
+ f0method0.change(
+ fn=switch_pitch_controls,
+ inputs=[f0method0],
+ outputs=[minpitch_slider, minpitch_txtbox,
+ maxpitch_slider, maxpitch_txtbox]
+ )
+
+ with gr.Column():
+ resample_sr0 = gr.Slider(
+ minimum=0,
+ maximum=48000,
+ label=i18n("Resample the output audio in post-processing to the final sample rate. Set to 0 for no resampling:"),
+ value=0,
+ step=1,
+ interactive=True,
+ )
+ rms_mix_rate0 = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label=i18n("Use the volume envelope of the input to replace or mix with the volume envelope of the output. The closer the ratio is to 1, the more the output envelope is used:"),
+ value=0.25,
+ interactive=True,
+ )
+ protect0 = gr.Slider(
+ minimum=0,
+ maximum=0.5,
+ label=i18n(
+ "Protect voiceless consonants and breath sounds to prevent artifacts such as tearing in electronic music. Set to 0.5 to disable. Decrease the value to increase protection, but it may reduce indexing accuracy:"
+ ),
+ value=0.33,
+ step=0.01,
+ interactive=True,
+ )
+ formanting = gr.Checkbox(
+ value=bool(DoFormant),
+ label=i18n("Formant shift inference audio"),
+ info=i18n("Used for male to female and vice-versa conversions"),
+ interactive=True,
+ visible=True,
+ )
+
+ formant_preset = gr.Dropdown(
+ value='',
+ choices=get_fshift_presets(),
+ label=i18n("Browse presets for formanting"),
+ info=i18n("Presets are located in formantshiftcfg/ folder"),
+ visible=bool(DoFormant),
+ )
+
+ formant_refresh_button = gr.Button(
+ value='\U0001f504',
+ visible=bool(DoFormant),
+ variant='primary',
+ )
+
+ qfrency = gr.Slider(
+ value=Quefrency,
+ info=i18n("Default value is 1.0"),
+ label=i18n("Quefrency for formant shifting"),
+ minimum=0.0,
+ maximum=16.0,
+ step=0.1,
+ visible=bool(DoFormant),
+ interactive=True,
+ )
+
+ tmbre = gr.Slider(
+ value=Timbre,
+ info=i18n("Default value is 1.0"),
+ label=i18n("Timbre for formant shifting"),
+ minimum=0.0,
+ maximum=16.0,
+ step=0.1,
+ visible=bool(DoFormant),
+ interactive=True,
+ )
+ frmntbut = gr.Button(
+ "Apply", variant="primary", visible=bool(DoFormant)
+ )
+
+ formant_preset.change(
+ fn=preset_apply,
+ inputs=[formant_preset, qfrency, tmbre],
+ outputs=[qfrency, tmbre],
+ )
+ formanting.change(
+ fn=formant_enabled,
+ inputs=[
+ formanting,
+ qfrency,
+ tmbre,
+ frmntbut,
+ formant_preset,
+ formant_refresh_button,
+ ],
+ outputs=[
+ formanting,
+ qfrency,
+ tmbre,
+ frmntbut,
+ formant_preset,
+ formant_refresh_button,
+ ],
+ )
+ frmntbut.click(
+ fn=formant_apply,
+ inputs=[qfrency, tmbre],
+ outputs=[qfrency, tmbre],
+ )
+ formant_refresh_button.click(
+ fn=update_fshift_presets,
+ inputs=[formant_preset, qfrency, tmbre],
+ outputs=[formant_preset, qfrency, tmbre],
+ )
+
+ # Function to toggle advanced settings
+ def toggle_advanced_settings(checkbox):
+ return {"visible": checkbox, "__type__": "update"}
+
+ # Attach the change event
+ advanced_settings_checkbox.change(
+ fn=toggle_advanced_settings,
+ inputs=[advanced_settings_checkbox],
+ outputs=[advanced_settings]
+ )
+
+
+ but0 = gr.Button(i18n("Convert"), variant="primary").style(full_width=True)
+
+ with gr.Row(): # Defines output info + output audio download after conversion
+ vc_output1 = gr.Textbox(label=i18n("Output information:"))
+ vc_output2 = gr.Audio(label=i18n("Export audio (click on the three dots in the lower right corner to download)"))
+
+ with gr.Group(): # I think this defines the big convert button
+ with gr.Row():
+ but0.click(
+ vc.vc_single,
+ [
+ spk_item,
+ input_audio0,
+ input_audio1,
+ vc_transform0,
+ f0_file,
+ f0method0,
+ file_index1,
+ file_index2,
+ index_rate1,
+ filter_radius0,
+ resample_sr0,
+ rms_mix_rate0,
+ protect0,
+ crepe_hop_length,
+ minpitch_slider, minpitch_txtbox,
+ maxpitch_slider, maxpitch_txtbox,
+ f0_autotune
+ ],
+ [vc_output1, vc_output2],
+ )
+
+
+ with gr.TabItem(i18n("Batch")): # Dont Change
+ with gr.Group(): # Markdown explanation of batch inference
+ gr.Markdown(
+ value=i18n("Batch conversion. Enter the folder containing the audio files to be converted or upload multiple audio files. The converted audio will be output in the specified folder (default: 'opt').")
+ )
+ with gr.Row():
+ with gr.Column():
+ vc_transform1 = gr.Number(
+ label=i18n("Transpose (integer, number of semitones, raise by an octave: 12, lower by an octave: -12):"), value=0
+ )
+ opt_input = gr.Textbox(label=i18n("Specify output folder:"), value="opt")
+ with gr.Column():
+ file_index4 = gr.Dropdown(
+ label=i18n("Auto-detect index path and select from the dropdown:"),
+ choices=get_indexes(),
+ value=best_match_index_path1,
+ interactive=True,
+ )
+ sid0.select(fn=match_index, inputs=[sid0], outputs=[file_index2, file_index4])
+
+ refresh_button.click(
+ fn=lambda: change_choices()[1],
+ inputs=[],
+ outputs=file_index4,
+ )
+ index_rate2 = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label=i18n("Search feature ratio:"),
+ value=0.75,
+ interactive=True,
+ )
+ with gr.Row():
+ dir_input = gr.Textbox(
+ label=i18n("Enter the path of the audio folder to be processed (copy it from the address bar of the file manager):"),
+ value=os.path.join(now_dir, "audios"),
+ )
+ inputs = gr.File(
+ file_count="multiple", label=i18n("You can also input audio files in batches. Choose one of the two options. Priority is given to reading from the folder.")
+ )
+
+ with gr.Row():
+ with gr.Column():
+ # Create a checkbox for advanced batch settings
+ advanced_settings_batch_checkbox = gr.Checkbox(
+ value=False,
+ label=i18n("Advanced Settings"),
+ interactive=True,
+ )
+
+ # Advanced batch settings container
+ with gr.Row(visible=False) as advanced_settings_batch: # Initially hidden
+ with gr.Row(label = i18n("Advanced Settings"), open = False):
+ with gr.Column():
+ file_index3 = gr.Textbox(
+ label=i18n("Feature search database file path:"),
+ value="",
+ interactive=True,
+ )
+
+ f0method1 = gr.Radio(
+ label=i18n(
+ "Select the pitch extraction algorithm:"
+ ),
+ choices=["pm", "harvest", "crepe", "rmvpe"],
+ value="rmvpe",
+ interactive=True,
+ )
+ f0_autotune = gr.Checkbox(
+ label="Enable autotune",
+ interactive=True
+ )
+ filter_radius1 = gr.Slider(
+ minimum=0,
+ maximum=7,
+ label=i18n("If >=3: apply median filtering to the harvested pitch results. The value represents the filter radius and can reduce breathiness."),
+ value=3,
+ step=1,
+ interactive=True,
+ )
+
+ with gr.Row():
+ format1 = gr.Radio(
+ label=i18n("Export file format"),
+ choices=["wav", "flac", "mp3", "m4a"],
+ value="wav",
+ interactive=True,
+ )
+
+
+ with gr.Column():
+ resample_sr1 = gr.Slider(
+ minimum=0,
+ maximum=48000,
+ label=i18n("Resample the output audio in post-processing to the final sample rate. Set to 0 for no resampling:"),
+ value=0,
+ step=1,
+ interactive=True,
+ )
+ rms_mix_rate1 = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label=i18n("Use the volume envelope of the input to replace or mix with the volume envelope of the output. The closer the ratio is to 1, the more the output envelope is used:"),
+ value=1,
+ interactive=True,
+ )
+ protect1 = gr.Slider(
+ minimum=0,
+ maximum=0.5,
+ label=i18n(
+ "Protect voiceless consonants and breath sounds to prevent artifacts such as tearing in electronic music. Set to 0.5 to disable. Decrease the value to increase protection, but it may reduce indexing accuracy:"
+ ),
+ value=0.33,
+ step=0.01,
+ interactive=True,
+ )
+ vc_output3 = gr.Textbox(label=i18n("Output information:"))
+ but1 = gr.Button(i18n("Convert"), variant="primary")
+ but1.click(
+ vc.vc_multi,
+ [
+ spk_item,
+ dir_input,
+ opt_input,
+ inputs,
+ vc_transform1,
+ f0method1,
+ file_index3,
+ file_index4,
+ index_rate2,
+ filter_radius1,
+ resample_sr1,
+ rms_mix_rate1,
+ protect1,
+ format1,
+ crepe_hop_length,
+ minpitch_slider if (not rvc_globals.NotesOrHertz) else minpitch_txtbox,
+ maxpitch_slider if (not rvc_globals.NotesOrHertz) else maxpitch_txtbox,
+ f0_autotune
+ ],
+ [vc_output3],
+ )
+
+ sid0.change(
+ fn=vc.get_vc,
+ inputs=[sid0, protect0, protect1],
+ outputs=[spk_item, protect0, protect1],
+ )
+ if not sid0.value == '':
+ spk_item, protect0, protect1 = vc.get_vc(sid0.value, protect0, protect1)
+
+ #spk_item, protect0, protect1 = vc.get_vc(sid0.value, protect0, protect1)
+
+ # Function to toggle advanced settings
+ def toggle_advanced_settings_batch(checkbox):
+ return {"visible": checkbox, "__type__": "update"}
+
+ # Attach the change event
+ advanced_settings_batch_checkbox.change(
+ fn=toggle_advanced_settings_batch,
+ inputs=[advanced_settings_batch_checkbox],
+ outputs=[advanced_settings_batch]
+ )
+
+
+ with gr.TabItem(i18n("Train")):
+
+
+ with gr.Accordion(label=i18n("Step 1: Processing data")):
+ with gr.Row():
+ exp_dir1 = gr.Textbox(label=i18n("Enter the model name:"), value=i18n("Model_Name"))
+ sr2 = gr.Radio(
+ label=i18n("Target sample rate:"),
+ choices=["40k", "48k", "32k"],
+ value="40k",
+ interactive=True,
+ )
+ if_f0_3 = gr.Checkbox(
+ label=i18n("Whether the model has pitch guidance."),
+ value=True,
+ interactive=True,
+ )
+ version19 = gr.Radio(
+ label=i18n("Version:"),
+ choices=["v1", "v2"],
+ value="v2",
+ interactive=True,
+ visible=True,
+ )
+ np7 = gr.Slider(
+ minimum=0,
+ maximum=config.n_cpu,
+ step=1,
+ label=i18n("Number of CPU processes:"),
+ value=int(np.ceil(config.n_cpu / 1.5)),
+ interactive=True,
+ )
+ with gr.Group():
+ with gr.Accordion(label=i18n("Step 2: Skipping pitch extraction")):
+
+ with gr.Row():
+ # trainset_dir4 = gr.Textbox(
+ # label=i18n("Enter the path of the training folder:"), value=os.path.join(now_dir, datasets_root)
+ # )
+ with gr.Column():
+ trainset_dir4 = gr.Dropdown(choices=sorted(datasets), label=i18n("Select your dataset:"), value=get_dataset())
+ btn_update_dataset_list = gr.Button(i18n("Update list"), variant="primary")
+ spk_id5 = gr.Slider(
+ minimum=0,
+ maximum=4,
+ step=1,
+ label=i18n("Specify the model ID:"),
+ value=0,
+ interactive=True,
+ )
+ btn_update_dataset_list.click(
+ easy_infer.update_dataset_list, [spk_id5], trainset_dir4
+ )
+ but1 = gr.Button(i18n("Process data"), variant="primary")
+ info1 = gr.Textbox(label=i18n("Output information:"), value="")
+ but1.click(
+ preprocess_dataset, [trainset_dir4, exp_dir1, sr2, np7], [info1]
+ )
+ with gr.Group():
+ with gr.Accordion(label=i18n("Step 3: Extracting features")):
+ with gr.Row():
+ with gr.Column():
+ gpus6 = gr.Textbox(
+ label=i18n("Provide the GPU index(es) separated by '-', like 0-1-2 for using GPUs 0, 1, and 2:"),
+ value=gpus,
+ interactive=True,
+ )
+ gpu_info9 = gr.Textbox(
+ label=i18n("GPU Information:"), value=gpu_info, visible=F0GPUVisible
+ )
+ with gr.Column():
+ f0method8 = gr.Radio(
+ label=i18n(
+ "Select the pitch extraction algorithm:"
+ ),
+ choices=["pm", "harvest", "dio", "crepe", "mangio-crepe", "rmvpe", "rmvpe_gpu"],
+ # [ MANGIO ]: Fork feature: Crepe on f0 extraction for training.
+ value="rmvpe",
+ interactive=True,
+ )
+ gpus_rmvpe = gr.Textbox(
+ label=i18n(
+ "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程"
+ ),
+ value="%s-%s" % (gpus, gpus),
+ interactive=True,
+ visible=F0GPUVisible,
+ )
+
+ extraction_crepe_hop_length = gr.Slider(
+ minimum=1,
+ maximum=512,
+ step=1,
+ label=i18n("Mangio-Crepe Hop Length (Only applies to mangio-crepe): Hop length refers to the time it takes for the speaker to jump to a dramatic pitch. Lower hop lengths take more time to infer but are more pitch accurate."),
+ value=64,
+ interactive=True,
+ visible=False,
+ )
+
+ f0method8.change(
+ fn=lambda radio: (
+ {
+ "visible": radio in ['mangio-crepe', 'mangio-crepe-tiny'],
+ "__type__": "update"
+ }
+ ),
+ inputs=[f0method8],
+ outputs=[extraction_crepe_hop_length]
+ )
+ f0method8.change(
+ fn=change_f0_method,
+ inputs=[f0method8],
+ outputs=[gpus_rmvpe],
+ )
+ but2 = gr.Button(i18n("Feature extraction"), variant="primary")
+ info2 = gr.Textbox(label=i18n("Output information:"), value="", max_lines=8, interactive=False)
+ but2.click(
+ extract_f0_feature,
+ [gpus6, np7, f0method8, if_f0_3, exp_dir1, version19, extraction_crepe_hop_length, gpus_rmvpe,],
+ [info2],
+ )
+ with gr.Group():
+ with gr.Row():
+ with gr.Accordion(label=i18n("Step 4: Model training started")):
+ with gr.Row():
+ save_epoch10 = gr.Slider(
+ minimum=1,
+ maximum=100,
+ step=1,
+ label=i18n("Save frequency:"),
+ value=10,
+ interactive=True,
+ visible=True,
+ )
+ total_epoch11 = gr.Slider(
+ minimum=1,
+ maximum=10000,
+ step=2,
+ label=i18n("Training epochs:"),
+ value=750,
+ interactive=True,
+ )
+ batch_size12 = gr.Slider(
+ minimum=1,
+ maximum=50,
+ step=1,
+ label=i18n("Batch size per GPU:"),
+ value=default_batch_size,
+ #value=20,
+ interactive=True,
+ )
+
+ with gr.Row():
+ if_save_latest13 = gr.Checkbox(
+ label=i18n("Whether to save only the latest .ckpt file to save hard drive space"),
+ value=True,
+ interactive=True,
+ )
+ if_cache_gpu17 = gr.Checkbox(
+ label=i18n("Cache all training sets to GPU memory. Caching small datasets (less than 10 minutes) can speed up training"),
+ value=False,
+ interactive=True,
+ )
+ if_save_every_weights18 = gr.Checkbox(
+ label=i18n("Save a small final model to the 'weights' folder at each save point"),
+ value=True,
+ interactive=True,
+ )
+
+ with gr.Row():
+ pretrained_G14 = gr.Textbox(
+ lines=4,
+ label=i18n("Load pre-trained base model G path:"),
+ value="assets/pretrained_v2/f0G40k.pth",
+ interactive=True,
+ )
+ pretrained_D15 = gr.Textbox(
+ lines=4,
+ label=i18n("Load pre-trained base model D path:"),
+ value="assets/pretrained_v2/f0D40k.pth",
+ interactive=True,
+ )
+ gpus16 = gr.Textbox(
+ label=i18n("Provide the GPU index(es) separated by '-', like 0-1-2 for using GPUs 0, 1, and 2:"),
+ value=gpus,
+ interactive=True,
+ )
+ sr2.change(
+ change_sr2,
+ [sr2, if_f0_3, version19],
+ [pretrained_G14, pretrained_D15],
+ )
+ version19.change(
+ change_version19,
+ [sr2, if_f0_3, version19],
+ [pretrained_G14, pretrained_D15, sr2],
+ )
+ if_f0_3.change(
+ fn=change_f0,
+ inputs=[if_f0_3, sr2, version19],
+ outputs=[f0method8, pretrained_G14, pretrained_D15],
+ )
+ if_f0_3.change(fn=lambda radio: (
+ {
+ "visible": radio in ['mangio-crepe', 'mangio-crepe-tiny'],
+ "__type__": "update"
+ }
+ ), inputs=[f0method8], outputs=[extraction_crepe_hop_length])
+
+ butstop = gr.Button(i18n("Stop training"),
+ variant='primary',
+ visible=False,
+ )
+ but3 = gr.Button(i18n("Train model"), variant="primary", visible=True)
+ but3.click(fn=stoptraining, inputs=[gr.Number(value=0, visible=False)], outputs=[but3, butstop])
+ butstop.click(fn=stoptraining, inputs=[gr.Number(value=1, visible=False)], outputs=[but3, butstop])
+
+
+ with gr.Column():
+ info3 = gr.Textbox(label=i18n("Output information:"), value="", max_lines=4)
+ save_action = gr.Dropdown(label=i18n("Save type"), choices=[i18n("Save all"),i18n("Save D and G"),i18n("Save voice")], value=i18n("Choose the method"), interactive=True)
+
+ but7 = gr.Button(i18n("Save model"), variant="primary")
+ but4 = gr.Button(i18n("Train feature index"), variant="primary")
+
+
+
+ if_save_every_weights18.change(
+ fn=lambda if_save_every_weights: (
+ {
+ "visible": if_save_every_weights,
+ "__type__": "update"
+ }
+ ),
+ inputs=[if_save_every_weights18],
+ outputs=[save_epoch10]
+ )
+
+ but3.click(
+ click_train,
+ [
+ exp_dir1,
+ sr2,
+ if_f0_3,
+ spk_id5,
+ save_epoch10,
+ total_epoch11,
+ batch_size12,
+ if_save_latest13,
+ pretrained_G14,
+ pretrained_D15,
+ gpus16,
+ if_cache_gpu17,
+ if_save_every_weights18,
+ version19,
+ ],
+ [info3, butstop, but3],
+ )
+
+ but4.click(train_index, [exp_dir1, version19], info3)
+ but7.click(easy_infer.save_model, [exp_dir1, save_action], info3)
+ with gr.Group():
+ with gr.Row():
+ with gr.Accordion(label=i18n("Step 5: Export lowest points on a graph of the model")):
+
+ lowestval_weight_dir = gr.Textbox(visible=False)
+ ds = gr.Textbox(visible=False)
+ weights_dir1 = gr.Textbox(visible=False, value=weights_dir)
+
+
+ with gr.Row():
+ amntlastmdls = gr.Slider(
+ minimum=1,
+ maximum=25,
+ label=i18n('How many lowest points to save:'),
+ value=3,
+ step=1,
+ interactive=True,
+ )
+ lpexport = gr.Button(
+ value=i18n('Export lowest points of a model'),
+ variant='primary',
+ )
+ lw_mdls = gr.File(
+ file_count="multiple",
+ label=i18n("Output models:"),
+ interactive=False,
+ ) #####
+
+ with gr.Row():
+ infolpex = gr.Textbox(label=i18n("Output information:"), value="", max_lines=10)
+ mdlbl = gr.Dataframe(label=i18n('Stats of selected models:'), datatype='number', type='pandas')
+
+ lpexport.click(
+ lambda model_name: os.path.join("logs", model_name, "lowestvals"),
+ inputs=[exp_dir1],
+ outputs=[lowestval_weight_dir]
+ )
+
+ lpexport.click(fn=tensorlowest.main, inputs=[exp_dir1, save_epoch10, amntlastmdls], outputs=[ds])
+
+ ds.change(
+ fn=tensorlowest.selectweights,
+ inputs=[exp_dir1, ds, weights_dir1, lowestval_weight_dir],
+ outputs=[infolpex, lw_mdls, mdlbl],
+ )
+ with gr.TabItem(i18n("UVR5")): # UVR section
+ with gr.Group():
+ with gr.Row():
+ with gr.Column():
+ model_select = gr.Radio(
+ label=i18n("Model Architecture:"),
+ choices=["VR", "MDX"],
+ value="VR",
+ interactive=True,
+ )
+ dir_wav_input = gr.Textbox(
+ label=i18n("Enter the path of the audio folder to be processed:"),
+ value=os.path.join(now_dir, "audios")
+ )
+ wav_inputs = gr.File(
+ file_count="multiple", label=i18n("You can also input audio files in batches. Choose one of the two options. Priority is given to reading from the folder.")
+ )
+
+ with gr.Column():
+ model_choose = gr.Dropdown(label=i18n("Model:"), choices=uvr5_names)
+ agg = gr.Slider(
+ minimum=0,
+ maximum=20,
+ step=1,
+ label="Vocal Extraction Aggressive",
+ value=10,
+ interactive=True,
+ visible=False,
+ )
+ opt_vocal_root = gr.Textbox(
+ label=i18n("Specify the output folder for vocals:"), value="opt"
+ )
+ opt_ins_root = gr.Textbox(
+ label=i18n("Specify the output folder for accompaniment:"), value="opt"
+ )
+ format0 = gr.Radio(
+ label=i18n("Export file format:"),
+ choices=["wav", "flac", "mp3", "m4a"],
+ value="flac",
+ interactive=True,
+ )
+ model_select.change(
+ fn=update_model_choices,
+ inputs=model_select,
+ outputs=model_choose,
+ )
+ but2 = gr.Button(i18n("Convert"), variant="primary")
+ vc_output4 = gr.Textbox(label=i18n("Output information:"))
+ #wav_inputs.upload(fn=save_to_wav2_edited, inputs=[wav_inputs], outputs=[])
+ but2.click(
+ uvr,
+ [
+ model_choose,
+ dir_wav_input,
+ opt_vocal_root,
+ wav_inputs,
+ opt_ins_root,
+ agg,
+ format0,
+ model_select
+ ],
+ [vc_output4],
+ )
+ with gr.TabItem(i18n("TTS")):
+ with gr.Group():
+ with gr.Column():
+ text_test = gr.Textbox(label=i18n("Text:"), placeholder=i18n("Enter the text you want to convert to voice..."), lines=6)
+
+ with gr.Group():
+ with gr.Row():
+ with gr.Column():
+ tts_methods_voice = ["Edge-tts", "Bark-tts"]
+ ttsmethod_test = gr.Dropdown(tts_methods_voice, value='Edge-tts', label = i18n('TTS Method:'), visible=True)
+ tts_test = gr.Dropdown(set_edge_voice, label = i18n('TTS Model:'), visible=True)
+ ttsmethod_test.change(
+ fn=update_tts_methods_voice,
+ inputs=ttsmethod_test,
+ outputs=tts_test,
+ )
+
+ with gr.Column():
+ model_voice_path07 = gr.Dropdown(label=i18n('RVC Model:'), choices=sorted(names), value=default_weight)
+ best_match_index_path1 = match_index(model_voice_path07.value)
+
+ file_index2_07 = gr.Dropdown(
+ label=i18n('Select the .index file:'),
+ choices=get_indexes(),
+ value=best_match_index_path1,
+ interactive=True,
+ allow_custom_value=True,
+ )
+ #transpose_test = gr.Number(label = i18n('Transpose (integer, number Fof semitones, raise by an octave: 12, lower by an octave: -12):'), value=0, visible=True, interactive= True)
+
+
+
+
+ with gr.Row():
+ refresh_button_ = gr.Button(i18n("Refresh"), variant="primary")
+ refresh_button_.click(fn=change_choices2, inputs=[], outputs=[model_voice_path07, file_index2_07])
+ with gr.Row():
+ original_ttsvoice = gr.Audio(label=i18n('Audio TTS:'))
+ ttsvoice = gr.Audio(label=i18n('Audio RVC:'))
+
+ with gr.Row():
+ button_test = gr.Button(i18n("Convert"), variant="primary")
+
+
+ button_test.click(make_test, inputs=[
+ text_test,
+ tts_test,
+ model_voice_path07,
+ file_index2_07,
+ #transpose_test,
+ vc_transform0,
+ f0method8,
+ index_rate1,
+ crepe_hop_length,
+ f0_autotune,
+ ttsmethod_test
+ ], outputs=[ttsvoice, original_ttsvoice])
+
+ with gr.TabItem(i18n("Resources")):
+ gr.Markdown(f"Limit download size is {os.getenv('MAX_DOWNLOAD_SIZE')} MB, duplicate the space for modify the limit")
+ easy_infer.download_model()
+ easy_infer.download_backup()
+ easy_infer.download_dataset(trainset_dir4)
+ easy_infer.download_audio()
+ easy_infer.youtube_separator()
+ with gr.TabItem(i18n("Extra")):
+ gr.Markdown(
+ value=i18n("This section contains some extra utilities that often may be in experimental phases")
+ )
+ with gr.TabItem(i18n("Merge Audios")):
+ with gr.Group():
+ gr.Markdown(
+ value="## " + i18n("Merge your generated audios with the instrumental")
+ )
+ gr.Markdown(value=".",visible=True)
+ gr.Markdown(value=".",visible=True)
+ with gr.Row():
+ with gr.Column():
+ dropbox = gr.File(label=i18n("Drag your audio here:"))
+ gr.Markdown(value=i18n("### Instrumental settings:"))
+ input_audio1 = gr.Dropdown(
+ label=i18n("Choose your instrumental:"),
+ choices=sorted(audio_others_paths),
+ value='',
+ interactive=True,
+ )
+ input_audio1_scale = gr.Slider(
+ minimum=0,
+ maximum=10,
+ label=i18n("Volume of the instrumental audio:"),
+ value=1.00,
+ interactive=True,
+ )
+ gr.Markdown(value=i18n("### Audio settings:"))
+ input_audio3 = gr.Dropdown(
+ label=i18n("Select the generated audio"),
+ choices=sorted(audio_paths),
+ value='',
+ interactive=True,
+ )
+ with gr.Row():
+ input_audio3_scale = gr.Slider(
+ minimum=0,
+ maximum=10,
+ label=i18n("Volume of the generated audio:"),
+ value=1.00,
+ interactive=True,
+ )
+
+ gr.Markdown(value=i18n("### Add the effects:"))
+ reverb_ = gr.Checkbox(
+ label=i18n("Reverb"),
+ value=False,
+ interactive=True,
+ )
+ compressor_ = gr.Checkbox(
+ label=i18n("Compressor"),
+ value=False,
+ interactive=True,
+ )
+ noise_gate_ = gr.Checkbox(
+ label=i18n("Noise Gate"),
+ value=False,
+ interactive=True,
+ )
+
+ butnone = gr.Button(i18n("Merge"), variant="primary").style(full_width=True)
+
+ vc_output1 = gr.Textbox(label=i18n("Output information:"))
+ vc_output2 = gr.Audio(label=i18n("Export audio (click on the three dots in the lower right corner to download)"), type='filepath')
+
+ dropbox.upload(fn=save_to_wav2, inputs=[dropbox], outputs=[input_audio1])
+ dropbox.upload(fn=easy_infer.change_choices2, inputs=[], outputs=[input_audio1])
+
+ refresh_button.click(
+ fn=lambda: change_choices3(),
+ inputs=[],
+ outputs=[input_audio1, input_audio3],
+ )
+
+ butnone.click(
+ fn=audio_combined,
+ inputs=[input_audio1, input_audio3,input_audio1_scale,input_audio3_scale,reverb_,compressor_,noise_gate_],
+ outputs=[vc_output1, vc_output2]
+ )
+
+
+ with gr.TabItem(i18n("Processing")):
+ with gr.Group():
+
+ with gr.Accordion(label=i18n("Model fusion, can be used to test timbre fusion")):
+ with gr.Row():
+ with gr.Column():
+ name_to_save0 = gr.Textbox(
+ label=i18n("Name:"),
+ value="",
+ max_lines=1,
+ interactive=True,
+ placeholder=i18n("Name for saving")
+ )
+ alpha_a = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label=i18n("Weight for Model A:"),
+ value=0.5,
+ interactive=True,
+ )
+ if_f0_ = gr.Checkbox(
+ label=i18n("Whether the model has pitch guidance."),
+ value=True,
+ interactive=True,
+ )
+ version_2 = gr.Radio(
+ label=i18n("Model architecture version:"),
+ choices=["v1", "v2"],
+ value="v2",
+ interactive=True,
+ )
+ sr_ = gr.Radio(
+ label=i18n("Target sample rate:"),
+ choices=["40k", "48k"],
+ value="40k",
+ interactive=True,
+ )
+
+
+ with gr.Column():
+ ckpt_a = gr.Textbox(label=i18n("Path to Model A:"), value="", interactive=True, placeholder=i18n("Path to model"))
+
+ ckpt_b = gr.Textbox(label=i18n("Path to Model B:"), value="", interactive=True, placeholder=i18n("Path to model"))
+
+ info__ = gr.Textbox(
+ label=i18n("Model information to be placed:"), value="", max_lines=8, interactive=True, placeholder=i18n("Model information to be placed")
+ )
+ info4 = gr.Textbox(label=i18n("Output information:"), value="", max_lines=8)
+
+
+ but6 = gr.Button(i18n("Fusion"), variant="primary")
+
+ but6.click(
+ merge,
+ [
+ ckpt_a,
+ ckpt_b,
+ alpha_a,
+ sr_,
+ if_f0_,
+ info__,
+ name_to_save0,
+ version_2,
+ ],
+ info4,
+ ) # def merge(path1,path2,alpha1,sr,f0,info):
+ with gr.Group():
+ with gr.Accordion(label=i18n("Modify model information")):
+ with gr.Row(): ######
+ with gr.Column():
+ ckpt_path0 = gr.Textbox(
+ label=i18n("Path to Model:"), value="", interactive=True, placeholder=i18n("Path to model")
+ )
+ info_ = gr.Textbox(
+ label=i18n("Model information to be modified:"), value="", max_lines=8, interactive=True, placeholder=i18n("Model information to be placed")
+ )
+
+ with gr.Column():
+ name_to_save1 = gr.Textbox(
+ label=i18n("Save file name:"),
+ placeholder=i18n("Name for saving"),
+ value="",
+ max_lines=8,
+ interactive=True,
+
+ )
+
+ info5 = gr.Textbox(label=i18n("Output information:"), value="", max_lines=8)
+ but7 = gr.Button(i18n("Modify"), variant="primary")
+ but7.click(change_info, [ckpt_path0, info_, name_to_save1], info5)
+ with gr.Group():
+ with gr.Accordion(label=i18n("View model information")):
+ with gr.Row():
+ with gr.Column():
+ ckpt_path1 = gr.Textbox(
+ label=i18n("Path to Model:"), value="", interactive=True, placeholder=i18n("Path to model")
+ )
+
+ info6 = gr.Textbox(label=i18n("Output information:"), value="", max_lines=8)
+ but8 = gr.Button(i18n("View"), variant="primary")
+ but8.click(show_info, [ckpt_path1], info6)
+ with gr.Group():
+ with gr.Accordion(label=i18n("Model extraction")):
+ with gr.Row():
+ with gr.Column():
+ save_name = gr.Textbox(
+ label=i18n("Name:"), value="", interactive=True, placeholder=i18n("Name for saving")
+ )
+ if_f0__ = gr.Checkbox(
+ label=i18n("Whether the model has pitch guidance."),
+ value=True,
+ interactive=True,
+ )
+ version_1 = gr.Radio(
+ label=i18n("Model architecture version:"),
+ choices=["v1", "v2"],
+ value="v2",
+ interactive=True,
+ )
+ sr__ = gr.Radio(
+ label=i18n("Target sample rate:"),
+ choices=["32k", "40k", "48k"],
+ value="40k",
+ interactive=True,
+ )
+
+ with gr.Column():
+ ckpt_path2 = gr.Textbox(
+
+ label=i18n("Path to Model:"),
+ placeholder=i18n("Path to model"),
+ interactive=True,
+ )
+ info___ = gr.Textbox(
+ label=i18n("Model information to be placed:"), value="", max_lines=8, interactive=True, placeholder=i18n("Model information to be placed")
+ )
+ info7 = gr.Textbox(label=i18n("Output information:"), value="", max_lines=8)
+
+ with gr.Row():
+
+ but9 = gr.Button(i18n("Extract"), variant="primary")
+ ckpt_path2.change(
+ change_info_, [ckpt_path2], [sr__, if_f0__, version_1]
+ )
+ but9.click(
+ extract_small_model,
+ [ckpt_path2, save_name, sr__, if_f0__, info___, version_1],
+ info7,
+ )
+
+
+
+
+ with gr.TabItem(i18n("Settings")):
+ with gr.Row():
+ gr.Markdown(value=
+ i18n("Pitch settings")
+ )
+ noteshertz = gr.Checkbox(
+ label = i18n("Whether to use note names instead of their hertz value. E.G. [C5, D6] instead of [523.25, 1174.66]Hz"),
+ value = rvc_globals.NotesOrHertz,
+ interactive = True,
+ )
+
+ noteshertz.change(fn=lambda nhertz: rvc_globals.__setattr__('NotesOrHertz', nhertz), inputs=[noteshertz], outputs=[])
+
+ noteshertz.change(
+ fn=switch_pitch_controls,
+ inputs=[f0method0],
+ outputs=[
+ minpitch_slider, minpitch_txtbox,
+ maxpitch_slider, maxpitch_txtbox,]
+ )
+ return app
+
+def GradioRun(app):
+ share_gradio_link = config.iscolab or config.paperspace
+ concurrency_count = 511
+ max_size = 1022
+
+ if (
+ config.iscolab or config.paperspace
+ ):
+ app.queue(concurrency_count=concurrency_count, max_size=max_size).launch(
+ favicon_path="./images/icon.png",
+ )
+ else:
+ app.queue(concurrency_count=concurrency_count, max_size=max_size).launch(
+ favicon_path=".\images\icon.png",
+ )
+
+if __name__ == "__main__":
+ if os.name == 'nt':
+ print(i18n("Any ConnectionResetErrors post-conversion are irrelevant and purely visual; they can be ignored.\n"))
+ app = GradioSetup(UTheme=config.grtheme)
+ GradioRun(app)
\ No newline at end of file
diff --git a/assets/hubert/.gitignore b/assets/hubert/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/assets/hubert/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/assets/pretrained/.gitignore b/assets/pretrained/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/assets/pretrained/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/assets/pretrained_v2/.gitignore b/assets/pretrained_v2/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/assets/pretrained_v2/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/assets/rmvpe/.gitignore b/assets/rmvpe/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/assets/rmvpe/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/assets/uvr5_weights/.gitignore b/assets/uvr5_weights/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/assets/uvr5_weights/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/assets/weights/.gitignore b/assets/weights/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/assets/weights/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/audioEffects.py b/audioEffects.py
new file mode 100644
index 0000000000000000000000000000000000000000..1830b19e1a5e3ec1f431388d8444ef3a2c9ed91f
--- /dev/null
+++ b/audioEffects.py
@@ -0,0 +1,37 @@
+from pedalboard import Pedalboard, Compressor, Reverb, NoiseGate
+from pedalboard.io import AudioFile
+import sys
+import os
+now_dir = os.getcwd()
+sys.path.append(now_dir)
+from i18n import I18nAuto
+i18n = I18nAuto()
+from pydub import AudioSegment
+import numpy as np
+import soundfile as sf
+from pydub.playback import play
+
+def process_audio(input_path, output_path, reverb_enabled, compressor_enabled, noise_gate_enabled, ):
+ print(reverb_enabled)
+ print(compressor_enabled)
+ print(noise_gate_enabled)
+ effects = []
+ if reverb_enabled:
+ effects.append(Reverb(room_size=0.01))
+ if compressor_enabled:
+ effects.append(Compressor(threshold_db=-10, ratio=25))
+ if noise_gate_enabled:
+ effects.append(NoiseGate(threshold_db=-16, ratio=1.5, release_ms=250))
+
+ board = Pedalboard(effects)
+
+ with AudioFile(input_path) as f:
+ with AudioFile(output_path, 'w', f.samplerate, f.num_channels) as o:
+ while f.tell() < f.frames:
+ chunk = f.read(f.samplerate)
+ effected = board(chunk, f.samplerate, reset=False)
+ o.write(effected)
+
+ result = i18n("Processed audio saved at: ") + output_path
+ print(result)
+ return output_path
\ No newline at end of file
diff --git a/audios/.gitignore b/audios/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/colab_for_mdx.py b/colab_for_mdx.py
new file mode 100644
index 0000000000000000000000000000000000000000..274846d0b5395865a05fce0da86b96d26ac06999
--- /dev/null
+++ b/colab_for_mdx.py
@@ -0,0 +1,71 @@
+import json
+import os
+import gc
+import psutil
+import requests
+import subprocess
+import time
+import logging
+import sys
+import shutil
+now_dir = os.getcwd()
+sys.path.append(now_dir)
+first_cell_executed = False
+file_folder = "Colab-for-MDX_B"
+def first_cell_ran():
+ global first_cell_executed
+ if first_cell_executed:
+ #print("The 'first_cell_ran' function has already been executed.")
+ return
+
+
+
+ first_cell_executed = True
+ os.makedirs("tmp_models", exist_ok=True)
+
+
+
+ class hide_opt: # hide outputs
+ def __enter__(self):
+ self._original_stdout = sys.stdout
+ sys.stdout = open(os.devnull, "w")
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ sys.stdout.close()
+ sys.stdout = self._original_stdout
+
+ def get_size(bytes, suffix="B"): # read ram
+ global svmem
+ factor = 1024
+ for unit in ["", "K", "M", "G", "T", "P"]:
+ if bytes < factor:
+ return f"{bytes:.2f}{unit}{suffix}"
+ bytes /= factor
+ svmem = psutil.virtual_memory()
+
+
+ def use_uvr_without_saving():
+ print("Notice: files won't be saved to personal drive.")
+ print(f"Downloading {file_folder}...", end=" ")
+ with hide_opt():
+ #os.chdir(mounting_path)
+ items_to_move = ["demucs", "diffq","julius","model","separated","tracks","mdx.py","MDX-Net_Colab.ipynb"]
+ subprocess.run(["git", "clone", "https://github.com/NaJeongMo/Colab-for-MDX_B.git"])
+ for item_name in items_to_move:
+ item_path = os.path.join(file_folder, item_name)
+ if os.path.exists(item_path):
+ if os.path.isfile(item_path):
+ shutil.move(item_path, now_dir)
+ elif os.path.isdir(item_path):
+ shutil.move(item_path, now_dir)
+ try:
+ shutil.rmtree(file_folder)
+ except PermissionError:
+ print(f"No se pudo eliminar la carpeta {file_folder}. Puede estar relacionada con Git.")
+
+
+ use_uvr_without_saving()
+ print("done!")
+ if not os.path.exists("tracks"):
+ os.mkdir("tracks")
+first_cell_ran()
\ No newline at end of file
diff --git a/configs/32k.json b/configs/32k.json
new file mode 100644
index 0000000000000000000000000000000000000000..bcae72223ec09dc199009d7cb5ed405a0c0981cf
--- /dev/null
+++ b/configs/32k.json
@@ -0,0 +1,50 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": false,
+ "lr_decay": 0.999875,
+ "segment_size": 12800,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 32000,
+ "filter_length": 1024,
+ "hop_length": 320,
+ "win_length": 1024,
+ "n_mel_channels": 80,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3, 7, 11],
+ "resblock_dilation_sizes": [
+ [1, 3, 5],
+ [1, 3, 5],
+ [1, 3, 5]
+ ],
+ "upsample_rates": [10, 4, 2, 2, 2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [16, 16, 4, 4, 4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/32k_v2.json b/configs/32k_v2.json
new file mode 100644
index 0000000000000000000000000000000000000000..ad42f87b15e1ea68eff0a90db50fbc08d56c7aa9
--- /dev/null
+++ b/configs/32k_v2.json
@@ -0,0 +1,50 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 12800,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 32000,
+ "filter_length": 1024,
+ "hop_length": 320,
+ "win_length": 1024,
+ "n_mel_channels": 80,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3, 7, 11],
+ "resblock_dilation_sizes": [
+ [1, 3, 5],
+ [1, 3, 5],
+ [1, 3, 5]
+ ],
+ "upsample_rates": [10, 8, 2, 2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [20, 16, 4, 4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/40k.json b/configs/40k.json
new file mode 100644
index 0000000000000000000000000000000000000000..28ff4d91f2618497fb39ad27872151bcb0d51761
--- /dev/null
+++ b/configs/40k.json
@@ -0,0 +1,50 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": false,
+ "lr_decay": 0.999875,
+ "segment_size": 12800,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 40000,
+ "filter_length": 2048,
+ "hop_length": 400,
+ "win_length": 2048,
+ "n_mel_channels": 125,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3, 7, 11],
+ "resblock_dilation_sizes": [
+ [1, 3, 5],
+ [1, 3, 5],
+ [1, 3, 5]
+ ],
+ "upsample_rates": [10, 10, 2, 2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [16, 16, 4, 4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/48k.json b/configs/48k.json
new file mode 100644
index 0000000000000000000000000000000000000000..4d01946ed50ade92c1f85b548ab008a4cd617eb8
--- /dev/null
+++ b/configs/48k.json
@@ -0,0 +1,50 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": false,
+ "lr_decay": 0.999875,
+ "segment_size": 11520,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 48000,
+ "filter_length": 2048,
+ "hop_length": 480,
+ "win_length": 2048,
+ "n_mel_channels": 128,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3, 7, 11],
+ "resblock_dilation_sizes": [
+ [1, 3, 5],
+ [1, 3, 5],
+ [1, 3, 5]
+ ],
+ "upsample_rates": [10, 6, 2, 2, 2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [16, 16, 4, 4, 4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/48k_v2.json b/configs/48k_v2.json
new file mode 100644
index 0000000000000000000000000000000000000000..50f06421912e2cd1f69768c35272981d75d86983
--- /dev/null
+++ b/configs/48k_v2.json
@@ -0,0 +1,50 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 17280,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 48000,
+ "filter_length": 2048,
+ "hop_length": 480,
+ "win_length": 2048,
+ "n_mel_channels": 128,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3, 7, 11],
+ "resblock_dilation_sizes": [
+ [1, 3, 5],
+ [1, 3, 5],
+ [1, 3, 5]
+ ],
+ "upsample_rates": [12, 10, 2, 2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [24, 20, 4, 4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/config.json b/configs/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..8e9c17669bf8028653fbfa5c9eeac23ec76c1cc9
--- /dev/null
+++ b/configs/config.json
@@ -0,0 +1,15 @@
+{
+ "pth_path": "assets/weights/kikiV1.pth",
+ "index_path": "logs/kikiV1.index",
+ "sg_input_device": "VoiceMeeter Output (VB-Audio Vo (MME)",
+ "sg_output_device": "VoiceMeeter Aux Input (VB-Audio (MME)",
+ "threhold": -45.0,
+ "pitch": 12.0,
+ "index_rate": 0.0,
+ "rms_mix_rate": 0.0,
+ "block_time": 0.25,
+ "crossfade_length": 0.04,
+ "extra_time": 2.0,
+ "n_cpu": 6.0,
+ "f0method": "rmvpe"
+}
diff --git a/configs/config.py b/configs/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3b0205a1f0d62f674b9c3de2c5ab7ee90464945
--- /dev/null
+++ b/configs/config.py
@@ -0,0 +1,265 @@
+import argparse
+import os
+import sys
+import json
+from multiprocessing import cpu_count
+
+import torch
+
+try:
+ import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
+ if torch.xpu.is_available():
+ from infer.modules.ipex import ipex_init
+ ipex_init()
+except Exception:
+ pass
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+version_config_list = [
+ "v1/32k.json",
+ "v1/40k.json",
+ "v1/48k.json",
+ "v2/48k.json",
+ "v2/32k.json",
+]
+
+
+def singleton_variable(func):
+ def wrapper(*args, **kwargs):
+ if not wrapper.instance:
+ wrapper.instance = func(*args, **kwargs)
+ return wrapper.instance
+
+ wrapper.instance = None
+ return wrapper
+
+
+@singleton_variable
+class Config:
+ def __init__(self):
+ self.device = "cuda:0"
+ self.is_half = True
+ self.n_cpu = 0
+ self.gpu_name = None
+ self.json_config = self.load_config_json()
+ self.gpu_mem = None
+ (
+ self.python_cmd,
+ self.listen_port,
+ self.iscolab,
+ self.noparallel,
+ self.noautoopen,
+ self.paperspace,
+ self.is_cli,
+ self.grtheme,
+ self.dml,
+ ) = self.arg_parse()
+ self.instead = ""
+ self.x_pad, self.x_query, self.x_center, self.x_max = self.device_config()
+
+ @staticmethod
+ def load_config_json() -> dict:
+ d = {}
+ for config_file in version_config_list:
+ with open(f"configs/{config_file}", "r") as f:
+ d[config_file] = json.load(f)
+ return d
+
+ @staticmethod
+ def arg_parse() -> tuple:
+ exe = sys.executable or "python"
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--port", type=int, default=7865, help="Listen port")
+ parser.add_argument("--pycmd", type=str, default=exe, help="Python command")
+ parser.add_argument("--colab", action="store_true", help="Launch in colab")
+ parser.add_argument(
+ "--noparallel", action="store_true", help="Disable parallel processing"
+ )
+ parser.add_argument(
+ "--noautoopen",
+ action="store_true",
+ help="Do not open in browser automatically",
+ )
+ parser.add_argument(
+ "--paperspace",
+ action="store_true",
+ help="Note that this argument just shares a gradio link for the web UI. Thus can be used on other non-local CLI systems.",
+ )
+ parser.add_argument(
+ "--is_cli",
+ action="store_true",
+ help="Use the CLI instead of setting up a gradio UI. This flag will launch an RVC text interface where you can execute functions from infer-web.py!",
+ )
+
+ parser.add_argument(
+ "-t",
+ "--theme",
+ help = "Theme for Gradio. Format - `JohnSmith9982/small_and_pretty` (no backticks)",
+ default = "JohnSmith9982/small_and_pretty",
+ type = str
+ )
+
+ parser.add_argument(
+ "--dml",
+ action="store_true",
+ help="Use DirectML backend instead of CUDA."
+ )
+
+ cmd_opts = parser.parse_args()
+
+ cmd_opts.port = cmd_opts.port if 0 <= cmd_opts.port <= 65535 else 7865
+
+ return (
+ cmd_opts.pycmd,
+ cmd_opts.port,
+ cmd_opts.colab,
+ cmd_opts.noparallel,
+ cmd_opts.noautoopen,
+ cmd_opts.paperspace,
+ cmd_opts.is_cli,
+ cmd_opts.theme,
+ cmd_opts.dml,
+ )
+
+ # has_mps is only available in nightly pytorch (for now) and MasOS 12.3+.
+ # check `getattr` and try it for compatibility
+ @staticmethod
+ def has_mps() -> bool:
+ if not torch.backends.mps.is_available():
+ return False
+ try:
+ torch.zeros(1).to(torch.device("mps"))
+ return True
+ except Exception:
+ return False
+
+ @staticmethod
+ def has_xpu() -> bool:
+ if hasattr(torch, "xpu") and torch.xpu.is_available():
+ return True
+ else:
+ return False
+
+ def use_fp32_config(self):
+ for config_file in version_config_list:
+ self.json_config[config_file]["train"]["fp16_run"] = False
+
+ def device_config(self) -> tuple:
+ if torch.cuda.is_available():
+ if self.has_xpu():
+ self.device = self.instead = "xpu:0"
+ self.is_half = True
+ i_device = int(self.device.split(":")[-1])
+ self.gpu_name = torch.cuda.get_device_name(i_device)
+ if (
+ ("16" in self.gpu_name and "V100" not in self.gpu_name.upper())
+ or "P40" in self.gpu_name.upper()
+ or "P10" in self.gpu_name.upper()
+ or "1060" in self.gpu_name
+ or "1070" in self.gpu_name
+ or "1080" in self.gpu_name
+ ):
+ logger.info("Found GPU %s, force to fp32", self.gpu_name)
+ self.is_half = False
+ self.use_fp32_config()
+ else:
+ logger.info("Found GPU %s", self.gpu_name)
+ self.gpu_mem = int(
+ torch.cuda.get_device_properties(i_device).total_memory
+ / 1024
+ / 1024
+ / 1024
+ + 0.4
+ )
+ if self.gpu_mem <= 4:
+ with open("infer/modules/train/preprocess.py", "r") as f:
+ strr = f.read().replace("3.7", "3.0")
+ with open("infer/modules/train/preprocess.py", "w") as f:
+ f.write(strr)
+ elif self.has_mps():
+ logger.info("No supported Nvidia GPU found")
+ self.device = self.instead = "mps"
+ self.is_half = False
+ self.use_fp32_config()
+ else:
+ logger.info("No supported Nvidia GPU found")
+ self.device = self.instead = "cpu"
+ self.is_half = False
+ self.use_fp32_config()
+
+ if self.n_cpu == 0:
+ self.n_cpu = cpu_count()
+
+ if self.is_half:
+ # 6G显存配置
+ x_pad = 3
+ x_query = 10
+ x_center = 60
+ x_max = 65
+ else:
+ # 5G显存配置
+ x_pad = 1
+ x_query = 6
+ x_center = 38
+ x_max = 41
+
+ if self.gpu_mem is not None and self.gpu_mem <= 4:
+ x_pad = 1
+ x_query = 5
+ x_center = 30
+ x_max = 32
+ if self.dml:
+ logger.info("Use DirectML instead")
+ if (
+ os.path.exists(
+ "runtime\Lib\site-packages\onnxruntime\capi\DirectML.dll"
+ )
+ == False
+ ):
+ try:
+ os.rename(
+ "runtime\Lib\site-packages\onnxruntime",
+ "runtime\Lib\site-packages\onnxruntime-cuda",
+ )
+ except:
+ pass
+ try:
+ os.rename(
+ "runtime\Lib\site-packages\onnxruntime-dml",
+ "runtime\Lib\site-packages\onnxruntime",
+ )
+ except:
+ pass
+ # if self.device != "cpu":
+ import torch_directml
+
+ self.device = torch_directml.device(torch_directml.default_device())
+ self.is_half = False
+ else:
+ if self.instead:
+ logger.info(f"Use {self.instead} instead")
+ if (
+ os.path.exists(
+ "runtime\Lib\site-packages\onnxruntime\capi\onnxruntime_providers_cuda.dll"
+ )
+ == False
+ ):
+ try:
+ os.rename(
+ "runtime\Lib\site-packages\onnxruntime",
+ "runtime\Lib\site-packages\onnxruntime-dml",
+ )
+ except:
+ pass
+ try:
+ os.rename(
+ "runtime\Lib\site-packages\onnxruntime-cuda",
+ "runtime\Lib\site-packages\onnxruntime",
+ )
+ except:
+ pass
+ return x_pad, x_query, x_center, x_max
diff --git a/configs/v1/32k.json b/configs/v1/32k.json
new file mode 100644
index 0000000000000000000000000000000000000000..d5f16d691ed798f4c974b431167c36269b2ce7d2
--- /dev/null
+++ b/configs/v1/32k.json
@@ -0,0 +1,46 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 12800,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 32000,
+ "filter_length": 1024,
+ "hop_length": 320,
+ "win_length": 1024,
+ "n_mel_channels": 80,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3,7,11],
+ "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]],
+ "upsample_rates": [10,4,2,2,2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [16,16,4,4,4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/v1/40k.json b/configs/v1/40k.json
new file mode 100644
index 0000000000000000000000000000000000000000..4ffc87b9e9725fcd59d81a68d41a61962213b777
--- /dev/null
+++ b/configs/v1/40k.json
@@ -0,0 +1,46 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 12800,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 40000,
+ "filter_length": 2048,
+ "hop_length": 400,
+ "win_length": 2048,
+ "n_mel_channels": 125,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3,7,11],
+ "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]],
+ "upsample_rates": [10,10,2,2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [16,16,4,4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/v1/48k.json b/configs/v1/48k.json
new file mode 100644
index 0000000000000000000000000000000000000000..2d0e05beb794f6f61b769b48c7ae728bf59e6335
--- /dev/null
+++ b/configs/v1/48k.json
@@ -0,0 +1,46 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 11520,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 48000,
+ "filter_length": 2048,
+ "hop_length": 480,
+ "win_length": 2048,
+ "n_mel_channels": 128,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3,7,11],
+ "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]],
+ "upsample_rates": [10,6,2,2,2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [16,16,4,4,4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/v2/32k.json b/configs/v2/32k.json
new file mode 100644
index 0000000000000000000000000000000000000000..70e534f4c641a5a2c8e5c1e172f61398ee97e6e0
--- /dev/null
+++ b/configs/v2/32k.json
@@ -0,0 +1,46 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 12800,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 32000,
+ "filter_length": 1024,
+ "hop_length": 320,
+ "win_length": 1024,
+ "n_mel_channels": 80,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3,7,11],
+ "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]],
+ "upsample_rates": [10,8,2,2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [20,16,4,4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/configs/v2/48k.json b/configs/v2/48k.json
new file mode 100644
index 0000000000000000000000000000000000000000..75f770cdacff3467e9e925ed2393b480881d0303
--- /dev/null
+++ b/configs/v2/48k.json
@@ -0,0 +1,46 @@
+{
+ "train": {
+ "log_interval": 200,
+ "seed": 1234,
+ "epochs": 20000,
+ "learning_rate": 1e-4,
+ "betas": [0.8, 0.99],
+ "eps": 1e-9,
+ "batch_size": 4,
+ "fp16_run": true,
+ "lr_decay": 0.999875,
+ "segment_size": 17280,
+ "init_lr_ratio": 1,
+ "warmup_epochs": 0,
+ "c_mel": 45,
+ "c_kl": 1.0
+ },
+ "data": {
+ "max_wav_value": 32768.0,
+ "sampling_rate": 48000,
+ "filter_length": 2048,
+ "hop_length": 480,
+ "win_length": 2048,
+ "n_mel_channels": 128,
+ "mel_fmin": 0.0,
+ "mel_fmax": null
+ },
+ "model": {
+ "inter_channels": 192,
+ "hidden_channels": 192,
+ "filter_channels": 768,
+ "n_heads": 2,
+ "n_layers": 6,
+ "kernel_size": 3,
+ "p_dropout": 0,
+ "resblock": "1",
+ "resblock_kernel_sizes": [3,7,11],
+ "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]],
+ "upsample_rates": [12,10,2,2],
+ "upsample_initial_channel": 512,
+ "upsample_kernel_sizes": [24,20,4,4],
+ "use_spectral_norm": false,
+ "gin_channels": 256,
+ "spk_embed_dim": 109
+ }
+}
diff --git a/csvdb/formanting.csv b/csvdb/formanting.csv
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/csvdb/stop.csv b/csvdb/stop.csv
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/demucs/__init__.py b/demucs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4182e356427e1b05a79f8da641c70bb732514fa
--- /dev/null
+++ b/demucs/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+__version__ = "2.0.3"
diff --git a/demucs/__main__.py b/demucs/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5148f20623bdaa827777558844796ded1876d7d0
--- /dev/null
+++ b/demucs/__main__.py
@@ -0,0 +1,317 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import json
+import math
+import os
+import sys
+import time
+from dataclasses import dataclass, field
+
+import torch as th
+from torch import distributed, nn
+from torch.nn.parallel.distributed import DistributedDataParallel
+
+from .augment import FlipChannels, FlipSign, Remix, Scale, Shift
+from .compressed import get_compressed_datasets
+from .model import Demucs
+from .parser import get_name, get_parser
+from .raw import Rawset
+from .repitch import RepitchedWrapper
+from .pretrained import load_pretrained, SOURCES
+from .tasnet import ConvTasNet
+from .test import evaluate
+from .train import train_model, validate_model
+from .utils import (human_seconds, load_model, save_model, get_state,
+ save_state, sizeof_fmt, get_quantizer)
+from .wav import get_wav_datasets, get_musdb_wav_datasets
+
+
+@dataclass
+class SavedState:
+ metrics: list = field(default_factory=list)
+ last_state: dict = None
+ best_state: dict = None
+ optimizer: dict = None
+
+
+def main():
+ parser = get_parser()
+ args = parser.parse_args()
+ name = get_name(parser, args)
+ print(f"Experiment {name}")
+
+ if args.musdb is None and args.rank == 0:
+ print(
+ "You must provide the path to the MusDB dataset with the --musdb flag. "
+ "To download the MusDB dataset, see https://sigsep.github.io/datasets/musdb.html.",
+ file=sys.stderr)
+ sys.exit(1)
+
+ eval_folder = args.evals / name
+ eval_folder.mkdir(exist_ok=True, parents=True)
+ args.logs.mkdir(exist_ok=True)
+ metrics_path = args.logs / f"{name}.json"
+ eval_folder.mkdir(exist_ok=True, parents=True)
+ args.checkpoints.mkdir(exist_ok=True, parents=True)
+ args.models.mkdir(exist_ok=True, parents=True)
+
+ if args.device is None:
+ device = "cpu"
+ if th.cuda.is_available():
+ device = "cuda"
+ else:
+ device = args.device
+
+ th.manual_seed(args.seed)
+ # Prevents too many threads to be started when running `museval` as it can be quite
+ # inefficient on NUMA architectures.
+ os.environ["OMP_NUM_THREADS"] = "1"
+ os.environ["MKL_NUM_THREADS"] = "1"
+
+ if args.world_size > 1:
+ if device != "cuda" and args.rank == 0:
+ print("Error: distributed training is only available with cuda device", file=sys.stderr)
+ sys.exit(1)
+ th.cuda.set_device(args.rank % th.cuda.device_count())
+ distributed.init_process_group(backend="nccl",
+ init_method="tcp://" + args.master,
+ rank=args.rank,
+ world_size=args.world_size)
+
+ checkpoint = args.checkpoints / f"{name}.th"
+ checkpoint_tmp = args.checkpoints / f"{name}.th.tmp"
+ if args.restart and checkpoint.exists() and args.rank == 0:
+ checkpoint.unlink()
+
+ if args.test or args.test_pretrained:
+ args.epochs = 1
+ args.repeat = 0
+ if args.test:
+ model = load_model(args.models / args.test)
+ else:
+ model = load_pretrained(args.test_pretrained)
+ elif args.tasnet:
+ model = ConvTasNet(audio_channels=args.audio_channels,
+ samplerate=args.samplerate, X=args.X,
+ segment_length=4 * args.samples,
+ sources=SOURCES)
+ else:
+ model = Demucs(
+ audio_channels=args.audio_channels,
+ channels=args.channels,
+ context=args.context,
+ depth=args.depth,
+ glu=args.glu,
+ growth=args.growth,
+ kernel_size=args.kernel_size,
+ lstm_layers=args.lstm_layers,
+ rescale=args.rescale,
+ rewrite=args.rewrite,
+ stride=args.conv_stride,
+ resample=args.resample,
+ normalize=args.normalize,
+ samplerate=args.samplerate,
+ segment_length=4 * args.samples,
+ sources=SOURCES,
+ )
+ model.to(device)
+ if args.init:
+ model.load_state_dict(load_pretrained(args.init).state_dict())
+
+ if args.show:
+ print(model)
+ size = sizeof_fmt(4 * sum(p.numel() for p in model.parameters()))
+ print(f"Model size {size}")
+ return
+
+ try:
+ saved = th.load(checkpoint, map_location='cpu')
+ except IOError:
+ saved = SavedState()
+
+ optimizer = th.optim.Adam(model.parameters(), lr=args.lr)
+
+ quantizer = None
+ quantizer = get_quantizer(model, args, optimizer)
+
+ if saved.last_state is not None:
+ model.load_state_dict(saved.last_state, strict=False)
+ if saved.optimizer is not None:
+ optimizer.load_state_dict(saved.optimizer)
+
+ model_name = f"{name}.th"
+ if args.save_model:
+ if args.rank == 0:
+ model.to("cpu")
+ model.load_state_dict(saved.best_state)
+ save_model(model, quantizer, args, args.models / model_name)
+ return
+ elif args.save_state:
+ model_name = f"{args.save_state}.th"
+ if args.rank == 0:
+ model.to("cpu")
+ model.load_state_dict(saved.best_state)
+ state = get_state(model, quantizer)
+ save_state(state, args.models / model_name)
+ return
+
+ if args.rank == 0:
+ done = args.logs / f"{name}.done"
+ if done.exists():
+ done.unlink()
+
+ augment = [Shift(args.data_stride)]
+ if args.augment:
+ augment += [FlipSign(), FlipChannels(), Scale(),
+ Remix(group_size=args.remix_group_size)]
+ augment = nn.Sequential(*augment).to(device)
+ print("Agumentation pipeline:", augment)
+
+ if args.mse:
+ criterion = nn.MSELoss()
+ else:
+ criterion = nn.L1Loss()
+
+ # Setting number of samples so that all convolution windows are full.
+ # Prevents hard to debug mistake with the prediction being shifted compared
+ # to the input mixture.
+ samples = model.valid_length(args.samples)
+ print(f"Number of training samples adjusted to {samples}")
+ samples = samples + args.data_stride
+ if args.repitch:
+ # We need a bit more audio samples, to account for potential
+ # tempo change.
+ samples = math.ceil(samples / (1 - 0.01 * args.max_tempo))
+
+ args.metadata.mkdir(exist_ok=True, parents=True)
+ if args.raw:
+ train_set = Rawset(args.raw / "train",
+ samples=samples,
+ channels=args.audio_channels,
+ streams=range(1, len(model.sources) + 1),
+ stride=args.data_stride)
+
+ valid_set = Rawset(args.raw / "valid", channels=args.audio_channels)
+ elif args.wav:
+ train_set, valid_set = get_wav_datasets(args, samples, model.sources)
+ elif args.is_wav:
+ train_set, valid_set = get_musdb_wav_datasets(args, samples, model.sources)
+ else:
+ train_set, valid_set = get_compressed_datasets(args, samples)
+
+ if args.repitch:
+ train_set = RepitchedWrapper(
+ train_set,
+ proba=args.repitch,
+ max_tempo=args.max_tempo)
+
+ best_loss = float("inf")
+ for epoch, metrics in enumerate(saved.metrics):
+ print(f"Epoch {epoch:03d}: "
+ f"train={metrics['train']:.8f} "
+ f"valid={metrics['valid']:.8f} "
+ f"best={metrics['best']:.4f} "
+ f"ms={metrics.get('true_model_size', 0):.2f}MB "
+ f"cms={metrics.get('compressed_model_size', 0):.2f}MB "
+ f"duration={human_seconds(metrics['duration'])}")
+ best_loss = metrics['best']
+
+ if args.world_size > 1:
+ dmodel = DistributedDataParallel(model,
+ device_ids=[th.cuda.current_device()],
+ output_device=th.cuda.current_device())
+ else:
+ dmodel = model
+
+ for epoch in range(len(saved.metrics), args.epochs):
+ begin = time.time()
+ model.train()
+ train_loss, model_size = train_model(
+ epoch, train_set, dmodel, criterion, optimizer, augment,
+ quantizer=quantizer,
+ batch_size=args.batch_size,
+ device=device,
+ repeat=args.repeat,
+ seed=args.seed,
+ diffq=args.diffq,
+ workers=args.workers,
+ world_size=args.world_size)
+ model.eval()
+ valid_loss = validate_model(
+ epoch, valid_set, model, criterion,
+ device=device,
+ rank=args.rank,
+ split=args.split_valid,
+ overlap=args.overlap,
+ world_size=args.world_size)
+
+ ms = 0
+ cms = 0
+ if quantizer and args.rank == 0:
+ ms = quantizer.true_model_size()
+ cms = quantizer.compressed_model_size(num_workers=min(40, args.world_size * 10))
+
+ duration = time.time() - begin
+ if valid_loss < best_loss and ms <= args.ms_target:
+ best_loss = valid_loss
+ saved.best_state = {
+ key: value.to("cpu").clone()
+ for key, value in model.state_dict().items()
+ }
+
+ saved.metrics.append({
+ "train": train_loss,
+ "valid": valid_loss,
+ "best": best_loss,
+ "duration": duration,
+ "model_size": model_size,
+ "true_model_size": ms,
+ "compressed_model_size": cms,
+ })
+ if args.rank == 0:
+ json.dump(saved.metrics, open(metrics_path, "w"))
+
+ saved.last_state = model.state_dict()
+ saved.optimizer = optimizer.state_dict()
+ if args.rank == 0 and not args.test:
+ th.save(saved, checkpoint_tmp)
+ checkpoint_tmp.rename(checkpoint)
+
+ print(f"Epoch {epoch:03d}: "
+ f"train={train_loss:.8f} valid={valid_loss:.8f} best={best_loss:.4f} ms={ms:.2f}MB "
+ f"cms={cms:.2f}MB "
+ f"duration={human_seconds(duration)}")
+
+ if args.world_size > 1:
+ distributed.barrier()
+
+ del dmodel
+ model.load_state_dict(saved.best_state)
+ if args.eval_cpu:
+ device = "cpu"
+ model.to(device)
+ model.eval()
+ evaluate(model, args.musdb, eval_folder,
+ is_wav=args.is_wav,
+ rank=args.rank,
+ world_size=args.world_size,
+ device=device,
+ save=args.save,
+ split=args.split_valid,
+ shifts=args.shifts,
+ overlap=args.overlap,
+ workers=args.eval_workers)
+ model.to("cpu")
+ if args.rank == 0:
+ if not (args.test or args.test_pretrained):
+ save_model(model, quantizer, args, args.models / model_name)
+ print("done")
+ done.write_text("done")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/demucs/audio.py b/demucs/audio.py
new file mode 100644
index 0000000000000000000000000000000000000000..b29f156e4afb5fbda32c35777022caeadf50d711
--- /dev/null
+++ b/demucs/audio.py
@@ -0,0 +1,172 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+import json
+import subprocess as sp
+from pathlib import Path
+
+import julius
+import numpy as np
+import torch
+
+from .utils import temp_filenames
+
+
+def _read_info(path):
+ stdout_data = sp.check_output([
+ 'ffprobe', "-loglevel", "panic",
+ str(path), '-print_format', 'json', '-show_format', '-show_streams'
+ ])
+ return json.loads(stdout_data.decode('utf-8'))
+
+
+class AudioFile:
+ """
+ Allows to read audio from any format supported by ffmpeg, as well as resampling or
+ converting to mono on the fly. See :method:`read` for more details.
+ """
+ def __init__(self, path: Path):
+ self.path = Path(path)
+ self._info = None
+
+ def __repr__(self):
+ features = [("path", self.path)]
+ features.append(("samplerate", self.samplerate()))
+ features.append(("channels", self.channels()))
+ features.append(("streams", len(self)))
+ features_str = ", ".join(f"{name}={value}" for name, value in features)
+ return f"AudioFile({features_str})"
+
+ @property
+ def info(self):
+ if self._info is None:
+ self._info = _read_info(self.path)
+ return self._info
+
+ @property
+ def duration(self):
+ return float(self.info['format']['duration'])
+
+ @property
+ def _audio_streams(self):
+ return [
+ index for index, stream in enumerate(self.info["streams"])
+ if stream["codec_type"] == "audio"
+ ]
+
+ def __len__(self):
+ return len(self._audio_streams)
+
+ def channels(self, stream=0):
+ return int(self.info['streams'][self._audio_streams[stream]]['channels'])
+
+ def samplerate(self, stream=0):
+ return int(self.info['streams'][self._audio_streams[stream]]['sample_rate'])
+
+ def read(self,
+ seek_time=None,
+ duration=None,
+ streams=slice(None),
+ samplerate=None,
+ channels=None,
+ temp_folder=None):
+ """
+ Slightly more efficient implementation than stempeg,
+ in particular, this will extract all stems at once
+ rather than having to loop over one file multiple times
+ for each stream.
+
+ Args:
+ seek_time (float): seek time in seconds or None if no seeking is needed.
+ duration (float): duration in seconds to extract or None to extract until the end.
+ streams (slice, int or list): streams to extract, can be a single int, a list or
+ a slice. If it is a slice or list, the output will be of size [S, C, T]
+ with S the number of streams, C the number of channels and T the number of samples.
+ If it is an int, the output will be [C, T].
+ samplerate (int): if provided, will resample on the fly. If None, no resampling will
+ be done. Original sampling rate can be obtained with :method:`samplerate`.
+ channels (int): if 1, will convert to mono. We do not rely on ffmpeg for that
+ as ffmpeg automatically scale by +3dB to conserve volume when playing on speakers.
+ See https://sound.stackexchange.com/a/42710.
+ Our definition of mono is simply the average of the two channels. Any other
+ value will be ignored.
+ temp_folder (str or Path or None): temporary folder to use for decoding.
+
+
+ """
+ streams = np.array(range(len(self)))[streams]
+ single = not isinstance(streams, np.ndarray)
+ if single:
+ streams = [streams]
+
+ if duration is None:
+ target_size = None
+ query_duration = None
+ else:
+ target_size = int((samplerate or self.samplerate()) * duration)
+ query_duration = float((target_size + 1) / (samplerate or self.samplerate()))
+
+ with temp_filenames(len(streams)) as filenames:
+ command = ['ffmpeg', '-y']
+ command += ['-loglevel', 'panic']
+ if seek_time:
+ command += ['-ss', str(seek_time)]
+ command += ['-i', str(self.path)]
+ for stream, filename in zip(streams, filenames):
+ command += ['-map', f'0:{self._audio_streams[stream]}']
+ if query_duration is not None:
+ command += ['-t', str(query_duration)]
+ command += ['-threads', '1']
+ command += ['-f', 'f32le']
+ if samplerate is not None:
+ command += ['-ar', str(samplerate)]
+ command += [filename]
+
+ sp.run(command, check=True)
+ wavs = []
+ for filename in filenames:
+ wav = np.fromfile(filename, dtype=np.float32)
+ wav = torch.from_numpy(wav)
+ wav = wav.view(-1, self.channels()).t()
+ if channels is not None:
+ wav = convert_audio_channels(wav, channels)
+ if target_size is not None:
+ wav = wav[..., :target_size]
+ wavs.append(wav)
+ wav = torch.stack(wavs, dim=0)
+ if single:
+ wav = wav[0]
+ return wav
+
+
+def convert_audio_channels(wav, channels=2):
+ """Convert audio to the given number of channels."""
+ *shape, src_channels, length = wav.shape
+ if src_channels == channels:
+ pass
+ elif channels == 1:
+ # Case 1:
+ # The caller asked 1-channel audio, but the stream have multiple
+ # channels, downmix all channels.
+ wav = wav.mean(dim=-2, keepdim=True)
+ elif src_channels == 1:
+ # Case 2:
+ # The caller asked for multiple channels, but the input file have
+ # one single channel, replicate the audio over all channels.
+ wav = wav.expand(*shape, channels, length)
+ elif src_channels >= channels:
+ # Case 3:
+ # The caller asked for multiple channels, and the input file have
+ # more channels than requested. In that case return the first channels.
+ wav = wav[..., :channels, :]
+ else:
+ # Case 4: What is a reasonable choice here?
+ raise ValueError('The audio file has less channels than requested but is not mono.')
+ return wav
+
+
+def convert_audio(wav, from_samplerate, to_samplerate, channels):
+ wav = convert_audio_channels(wav, channels)
+ return julius.resample_frac(wav, from_samplerate, to_samplerate)
diff --git a/demucs/augment.py b/demucs/augment.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb36d3298d89470f306316322e7587187819c94b
--- /dev/null
+++ b/demucs/augment.py
@@ -0,0 +1,106 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import random
+import torch as th
+from torch import nn
+
+
+class Shift(nn.Module):
+ """
+ Randomly shift audio in time by up to `shift` samples.
+ """
+ def __init__(self, shift=8192):
+ super().__init__()
+ self.shift = shift
+
+ def forward(self, wav):
+ batch, sources, channels, time = wav.size()
+ length = time - self.shift
+ if self.shift > 0:
+ if not self.training:
+ wav = wav[..., :length]
+ else:
+ offsets = th.randint(self.shift, [batch, sources, 1, 1], device=wav.device)
+ offsets = offsets.expand(-1, -1, channels, -1)
+ indexes = th.arange(length, device=wav.device)
+ wav = wav.gather(3, indexes + offsets)
+ return wav
+
+
+class FlipChannels(nn.Module):
+ """
+ Flip left-right channels.
+ """
+ def forward(self, wav):
+ batch, sources, channels, time = wav.size()
+ if self.training and wav.size(2) == 2:
+ left = th.randint(2, (batch, sources, 1, 1), device=wav.device)
+ left = left.expand(-1, -1, -1, time)
+ right = 1 - left
+ wav = th.cat([wav.gather(2, left), wav.gather(2, right)], dim=2)
+ return wav
+
+
+class FlipSign(nn.Module):
+ """
+ Random sign flip.
+ """
+ def forward(self, wav):
+ batch, sources, channels, time = wav.size()
+ if self.training:
+ signs = th.randint(2, (batch, sources, 1, 1), device=wav.device, dtype=th.float32)
+ wav = wav * (2 * signs - 1)
+ return wav
+
+
+class Remix(nn.Module):
+ """
+ Shuffle sources to make new mixes.
+ """
+ def __init__(self, group_size=4):
+ """
+ Shuffle sources within one batch.
+ Each batch is divided into groups of size `group_size` and shuffling is done within
+ each group separatly. This allow to keep the same probability distribution no matter
+ the number of GPUs. Without this grouping, using more GPUs would lead to a higher
+ probability of keeping two sources from the same track together which can impact
+ performance.
+ """
+ super().__init__()
+ self.group_size = group_size
+
+ def forward(self, wav):
+ batch, streams, channels, time = wav.size()
+ device = wav.device
+
+ if self.training:
+ group_size = self.group_size or batch
+ if batch % group_size != 0:
+ raise ValueError(f"Batch size {batch} must be divisible by group size {group_size}")
+ groups = batch // group_size
+ wav = wav.view(groups, group_size, streams, channels, time)
+ permutations = th.argsort(th.rand(groups, group_size, streams, 1, 1, device=device),
+ dim=1)
+ wav = wav.gather(1, permutations.expand(-1, -1, -1, channels, time))
+ wav = wav.view(batch, streams, channels, time)
+ return wav
+
+
+class Scale(nn.Module):
+ def __init__(self, proba=1., min=0.25, max=1.25):
+ super().__init__()
+ self.proba = proba
+ self.min = min
+ self.max = max
+
+ def forward(self, wav):
+ batch, streams, channels, time = wav.size()
+ device = wav.device
+ if self.training and random.random() < self.proba:
+ scales = th.empty(batch, streams, 1, 1, device=device).uniform_(self.min, self.max)
+ wav *= scales
+ return wav
diff --git a/demucs/compressed.py b/demucs/compressed.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb8fbb75463ba71ca86729b22baebf24598ade57
--- /dev/null
+++ b/demucs/compressed.py
@@ -0,0 +1,115 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import json
+from fractions import Fraction
+from concurrent import futures
+
+import musdb
+from torch import distributed
+
+from .audio import AudioFile
+
+
+def get_musdb_tracks(root, *args, **kwargs):
+ mus = musdb.DB(root, *args, **kwargs)
+ return {track.name: track.path for track in mus}
+
+
+class StemsSet:
+ def __init__(self, tracks, metadata, duration=None, stride=1,
+ samplerate=44100, channels=2, streams=slice(None)):
+
+ self.metadata = []
+ for name, path in tracks.items():
+ meta = dict(metadata[name])
+ meta["path"] = path
+ meta["name"] = name
+ self.metadata.append(meta)
+ if duration is not None and meta["duration"] < duration:
+ raise ValueError(f"Track {name} duration is too small {meta['duration']}")
+ self.metadata.sort(key=lambda x: x["name"])
+ self.duration = duration
+ self.stride = stride
+ self.channels = channels
+ self.samplerate = samplerate
+ self.streams = streams
+
+ def __len__(self):
+ return sum(self._examples_count(m) for m in self.metadata)
+
+ def _examples_count(self, meta):
+ if self.duration is None:
+ return 1
+ else:
+ return int((meta["duration"] - self.duration) // self.stride + 1)
+
+ def track_metadata(self, index):
+ for meta in self.metadata:
+ examples = self._examples_count(meta)
+ if index >= examples:
+ index -= examples
+ continue
+ return meta
+
+ def __getitem__(self, index):
+ for meta in self.metadata:
+ examples = self._examples_count(meta)
+ if index >= examples:
+ index -= examples
+ continue
+ streams = AudioFile(meta["path"]).read(seek_time=index * self.stride,
+ duration=self.duration,
+ channels=self.channels,
+ samplerate=self.samplerate,
+ streams=self.streams)
+ return (streams - meta["mean"]) / meta["std"]
+
+
+def _get_track_metadata(path):
+ # use mono at 44kHz as reference. For any other settings data won't be perfectly
+ # normalized but it should be good enough.
+ audio = AudioFile(path)
+ mix = audio.read(streams=0, channels=1, samplerate=44100)
+ return {"duration": audio.duration, "std": mix.std().item(), "mean": mix.mean().item()}
+
+
+def _build_metadata(tracks, workers=10):
+ pendings = []
+ with futures.ProcessPoolExecutor(workers) as pool:
+ for name, path in tracks.items():
+ pendings.append((name, pool.submit(_get_track_metadata, path)))
+ return {name: p.result() for name, p in pendings}
+
+
+def _build_musdb_metadata(path, musdb, workers):
+ tracks = get_musdb_tracks(musdb)
+ metadata = _build_metadata(tracks, workers)
+ path.parent.mkdir(exist_ok=True, parents=True)
+ json.dump(metadata, open(path, "w"))
+
+
+def get_compressed_datasets(args, samples):
+ metadata_file = args.metadata / "musdb.json"
+ if not metadata_file.is_file() and args.rank == 0:
+ _build_musdb_metadata(metadata_file, args.musdb, args.workers)
+ if args.world_size > 1:
+ distributed.barrier()
+ metadata = json.load(open(metadata_file))
+ duration = Fraction(samples, args.samplerate)
+ stride = Fraction(args.data_stride, args.samplerate)
+ train_set = StemsSet(get_musdb_tracks(args.musdb, subsets=["train"], split="train"),
+ metadata,
+ duration=duration,
+ stride=stride,
+ streams=slice(1, None),
+ samplerate=args.samplerate,
+ channels=args.audio_channels)
+ valid_set = StemsSet(get_musdb_tracks(args.musdb, subsets=["train"], split="valid"),
+ metadata,
+ samplerate=args.samplerate,
+ channels=args.audio_channels)
+ return train_set, valid_set
diff --git a/demucs/model.py b/demucs/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9d932f4d014f7b95b394d2e24ed5edc379ded8d
--- /dev/null
+++ b/demucs/model.py
@@ -0,0 +1,202 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import math
+
+import julius
+from torch import nn
+
+from .utils import capture_init, center_trim
+
+
+class BLSTM(nn.Module):
+ def __init__(self, dim, layers=1):
+ super().__init__()
+ self.lstm = nn.LSTM(bidirectional=True, num_layers=layers, hidden_size=dim, input_size=dim)
+ self.linear = nn.Linear(2 * dim, dim)
+
+ def forward(self, x):
+ x = x.permute(2, 0, 1)
+ x = self.lstm(x)[0]
+ x = self.linear(x)
+ x = x.permute(1, 2, 0)
+ return x
+
+
+def rescale_conv(conv, reference):
+ std = conv.weight.std().detach()
+ scale = (std / reference)**0.5
+ conv.weight.data /= scale
+ if conv.bias is not None:
+ conv.bias.data /= scale
+
+
+def rescale_module(module, reference):
+ for sub in module.modules():
+ if isinstance(sub, (nn.Conv1d, nn.ConvTranspose1d)):
+ rescale_conv(sub, reference)
+
+
+class Demucs(nn.Module):
+ @capture_init
+ def __init__(self,
+ sources,
+ audio_channels=2,
+ channels=64,
+ depth=6,
+ rewrite=True,
+ glu=True,
+ rescale=0.1,
+ resample=True,
+ kernel_size=8,
+ stride=4,
+ growth=2.,
+ lstm_layers=2,
+ context=3,
+ normalize=False,
+ samplerate=44100,
+ segment_length=4 * 10 * 44100):
+ """
+ Args:
+ sources (list[str]): list of source names
+ audio_channels (int): stereo or mono
+ channels (int): first convolution channels
+ depth (int): number of encoder/decoder layers
+ rewrite (bool): add 1x1 convolution to each encoder layer
+ and a convolution to each decoder layer.
+ For the decoder layer, `context` gives the kernel size.
+ glu (bool): use glu instead of ReLU
+ resample_input (bool): upsample x2 the input and downsample /2 the output.
+ rescale (int): rescale initial weights of convolutions
+ to get their standard deviation closer to `rescale`
+ kernel_size (int): kernel size for convolutions
+ stride (int): stride for convolutions
+ growth (float): multiply (resp divide) number of channels by that
+ for each layer of the encoder (resp decoder)
+ lstm_layers (int): number of lstm layers, 0 = no lstm
+ context (int): kernel size of the convolution in the
+ decoder before the transposed convolution. If > 1,
+ will provide some context from neighboring time
+ steps.
+ samplerate (int): stored as meta information for easing
+ future evaluations of the model.
+ segment_length (int): stored as meta information for easing
+ future evaluations of the model. Length of the segments on which
+ the model was trained.
+ """
+
+ super().__init__()
+ self.audio_channels = audio_channels
+ self.sources = sources
+ self.kernel_size = kernel_size
+ self.context = context
+ self.stride = stride
+ self.depth = depth
+ self.resample = resample
+ self.channels = channels
+ self.normalize = normalize
+ self.samplerate = samplerate
+ self.segment_length = segment_length
+
+ self.encoder = nn.ModuleList()
+ self.decoder = nn.ModuleList()
+
+ if glu:
+ activation = nn.GLU(dim=1)
+ ch_scale = 2
+ else:
+ activation = nn.ReLU()
+ ch_scale = 1
+ in_channels = audio_channels
+ for index in range(depth):
+ encode = []
+ encode += [nn.Conv1d(in_channels, channels, kernel_size, stride), nn.ReLU()]
+ if rewrite:
+ encode += [nn.Conv1d(channels, ch_scale * channels, 1), activation]
+ self.encoder.append(nn.Sequential(*encode))
+
+ decode = []
+ if index > 0:
+ out_channels = in_channels
+ else:
+ out_channels = len(self.sources) * audio_channels
+ if rewrite:
+ decode += [nn.Conv1d(channels, ch_scale * channels, context), activation]
+ decode += [nn.ConvTranspose1d(channels, out_channels, kernel_size, stride)]
+ if index > 0:
+ decode.append(nn.ReLU())
+ self.decoder.insert(0, nn.Sequential(*decode))
+ in_channels = channels
+ channels = int(growth * channels)
+
+ channels = in_channels
+
+ if lstm_layers:
+ self.lstm = BLSTM(channels, lstm_layers)
+ else:
+ self.lstm = None
+
+ if rescale:
+ rescale_module(self, reference=rescale)
+
+ def valid_length(self, length):
+ """
+ Return the nearest valid length to use with the model so that
+ there is no time steps left over in a convolutions, e.g. for all
+ layers, size of the input - kernel_size % stride = 0.
+
+ If the mixture has a valid length, the estimated sources
+ will have exactly the same length when context = 1. If context > 1,
+ the two signals can be center trimmed to match.
+
+ For training, extracts should have a valid length.For evaluation
+ on full tracks we recommend passing `pad = True` to :method:`forward`.
+ """
+ if self.resample:
+ length *= 2
+ for _ in range(self.depth):
+ length = math.ceil((length - self.kernel_size) / self.stride) + 1
+ length = max(1, length)
+ length += self.context - 1
+ for _ in range(self.depth):
+ length = (length - 1) * self.stride + self.kernel_size
+
+ if self.resample:
+ length = math.ceil(length / 2)
+ return int(length)
+
+ def forward(self, mix):
+ x = mix
+
+ if self.normalize:
+ mono = mix.mean(dim=1, keepdim=True)
+ mean = mono.mean(dim=-1, keepdim=True)
+ std = mono.std(dim=-1, keepdim=True)
+ else:
+ mean = 0
+ std = 1
+
+ x = (x - mean) / (1e-5 + std)
+
+ if self.resample:
+ x = julius.resample_frac(x, 1, 2)
+
+ saved = []
+ for encode in self.encoder:
+ x = encode(x)
+ saved.append(x)
+ if self.lstm:
+ x = self.lstm(x)
+ for decode in self.decoder:
+ skip = center_trim(saved.pop(-1), x)
+ x = x + skip
+ x = decode(x)
+
+ if self.resample:
+ x = julius.resample_frac(x, 2, 1)
+ x = x * std + mean
+ x = x.view(x.size(0), len(self.sources), self.audio_channels, x.size(-1))
+ return x
diff --git a/demucs/parser.py b/demucs/parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e8a19cf976e3c6dfe411da64b8dce3e9a4548e0
--- /dev/null
+++ b/demucs/parser.py
@@ -0,0 +1,244 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import argparse
+import os
+from pathlib import Path
+
+
+def get_parser():
+ parser = argparse.ArgumentParser("demucs", description="Train and evaluate Demucs.")
+ default_raw = None
+ default_musdb = None
+ if 'DEMUCS_RAW' in os.environ:
+ default_raw = Path(os.environ['DEMUCS_RAW'])
+ if 'DEMUCS_MUSDB' in os.environ:
+ default_musdb = Path(os.environ['DEMUCS_MUSDB'])
+ parser.add_argument(
+ "--raw",
+ type=Path,
+ default=default_raw,
+ help="Path to raw audio, can be faster, see python3 -m demucs.raw to extract.")
+ parser.add_argument("--no_raw", action="store_const", const=None, dest="raw")
+ parser.add_argument("-m",
+ "--musdb",
+ type=Path,
+ default=default_musdb,
+ help="Path to musdb root")
+ parser.add_argument("--is_wav", action="store_true",
+ help="Indicate that the MusDB dataset is in wav format (i.e. MusDB-HQ).")
+ parser.add_argument("--metadata", type=Path, default=Path("metadata/"),
+ help="Folder where metadata information is stored.")
+ parser.add_argument("--wav", type=Path,
+ help="Path to a wav dataset. This should contain a 'train' and a 'valid' "
+ "subfolder.")
+ parser.add_argument("--samplerate", type=int, default=44100)
+ parser.add_argument("--audio_channels", type=int, default=2)
+ parser.add_argument("--samples",
+ default=44100 * 10,
+ type=int,
+ help="number of samples to feed in")
+ parser.add_argument("--data_stride",
+ default=44100,
+ type=int,
+ help="Stride for chunks, shorter = longer epochs")
+ parser.add_argument("-w", "--workers", default=10, type=int, help="Loader workers")
+ parser.add_argument("--eval_workers", default=2, type=int, help="Final evaluation workers")
+ parser.add_argument("-d",
+ "--device",
+ help="Device to train on, default is cuda if available else cpu")
+ parser.add_argument("--eval_cpu", action="store_true", help="Eval on test will be run on cpu.")
+ parser.add_argument("--dummy", help="Dummy parameter, useful to create a new checkpoint file")
+ parser.add_argument("--test", help="Just run the test pipeline + one validation. "
+ "This should be a filename relative to the models/ folder.")
+ parser.add_argument("--test_pretrained", help="Just run the test pipeline + one validation, "
+ "on a pretrained model. ")
+
+ parser.add_argument("--rank", default=0, type=int)
+ parser.add_argument("--world_size", default=1, type=int)
+ parser.add_argument("--master")
+
+ parser.add_argument("--checkpoints",
+ type=Path,
+ default=Path("checkpoints"),
+ help="Folder where to store checkpoints etc")
+ parser.add_argument("--evals",
+ type=Path,
+ default=Path("evals"),
+ help="Folder where to store evals and waveforms")
+ parser.add_argument("--save",
+ action="store_true",
+ help="Save estimated for the test set waveforms")
+ parser.add_argument("--logs",
+ type=Path,
+ default=Path("logs"),
+ help="Folder where to store logs")
+ parser.add_argument("--models",
+ type=Path,
+ default=Path("models"),
+ help="Folder where to store trained models")
+ parser.add_argument("-R",
+ "--restart",
+ action='store_true',
+ help='Restart training, ignoring previous run')
+
+ parser.add_argument("--seed", type=int, default=42)
+ parser.add_argument("-e", "--epochs", type=int, default=180, help="Number of epochs")
+ parser.add_argument("-r",
+ "--repeat",
+ type=int,
+ default=2,
+ help="Repeat the train set, longer epochs")
+ parser.add_argument("-b", "--batch_size", type=int, default=64)
+ parser.add_argument("--lr", type=float, default=3e-4)
+ parser.add_argument("--mse", action="store_true", help="Use MSE instead of L1")
+ parser.add_argument("--init", help="Initialize from a pre-trained model.")
+
+ # Augmentation options
+ parser.add_argument("--no_augment",
+ action="store_false",
+ dest="augment",
+ default=True,
+ help="No basic data augmentation.")
+ parser.add_argument("--repitch", type=float, default=0.2,
+ help="Probability to do tempo/pitch change")
+ parser.add_argument("--max_tempo", type=float, default=12,
+ help="Maximum relative tempo change in %% when using repitch.")
+
+ parser.add_argument("--remix_group_size",
+ type=int,
+ default=4,
+ help="Shuffle sources using group of this size. Useful to somewhat "
+ "replicate multi-gpu training "
+ "on less GPUs.")
+ parser.add_argument("--shifts",
+ type=int,
+ default=10,
+ help="Number of random shifts used for the shift trick.")
+ parser.add_argument("--overlap",
+ type=float,
+ default=0.25,
+ help="Overlap when --split_valid is passed.")
+
+ # See model.py for doc
+ parser.add_argument("--growth",
+ type=float,
+ default=2.,
+ help="Number of channels between two layers will increase by this factor")
+ parser.add_argument("--depth",
+ type=int,
+ default=6,
+ help="Number of layers for the encoder and decoder")
+ parser.add_argument("--lstm_layers", type=int, default=2, help="Number of layers for the LSTM")
+ parser.add_argument("--channels",
+ type=int,
+ default=64,
+ help="Number of channels for the first encoder layer")
+ parser.add_argument("--kernel_size",
+ type=int,
+ default=8,
+ help="Kernel size for the (transposed) convolutions")
+ parser.add_argument("--conv_stride",
+ type=int,
+ default=4,
+ help="Stride for the (transposed) convolutions")
+ parser.add_argument("--context",
+ type=int,
+ default=3,
+ help="Context size for the decoder convolutions "
+ "before the transposed convolutions")
+ parser.add_argument("--rescale",
+ type=float,
+ default=0.1,
+ help="Initial weight rescale reference")
+ parser.add_argument("--no_resample", action="store_false",
+ default=True, dest="resample",
+ help="No Resampling of the input/output x2")
+ parser.add_argument("--no_glu",
+ action="store_false",
+ default=True,
+ dest="glu",
+ help="Replace all GLUs by ReLUs")
+ parser.add_argument("--no_rewrite",
+ action="store_false",
+ default=True,
+ dest="rewrite",
+ help="No 1x1 rewrite convolutions")
+ parser.add_argument("--normalize", action="store_true")
+ parser.add_argument("--no_norm_wav", action="store_false", dest='norm_wav', default=True)
+
+ # Tasnet options
+ parser.add_argument("--tasnet", action="store_true")
+ parser.add_argument("--split_valid",
+ action="store_true",
+ help="Predict chunks by chunks for valid and test. Required for tasnet")
+ parser.add_argument("--X", type=int, default=8)
+
+ # Other options
+ parser.add_argument("--show",
+ action="store_true",
+ help="Show model architecture, size and exit")
+ parser.add_argument("--save_model", action="store_true",
+ help="Skip traning, just save final model "
+ "for the current checkpoint value.")
+ parser.add_argument("--save_state",
+ help="Skip training, just save state "
+ "for the current checkpoint value. You should "
+ "provide a model name as argument.")
+
+ # Quantization options
+ parser.add_argument("--q-min-size", type=float, default=1,
+ help="Only quantize layers over this size (in MB)")
+ parser.add_argument(
+ "--qat", type=int, help="If provided, use QAT training with that many bits.")
+
+ parser.add_argument("--diffq", type=float, default=0)
+ parser.add_argument(
+ "--ms-target", type=float, default=162,
+ help="Model size target in MB, when using DiffQ. Best model will be kept "
+ "only if it is smaller than this target.")
+
+ return parser
+
+
+def get_name(parser, args):
+ """
+ Return the name of an experiment given the args. Some parameters are ignored,
+ for instance --workers, as they do not impact the final result.
+ """
+ ignore_args = set([
+ "checkpoints",
+ "deterministic",
+ "eval",
+ "evals",
+ "eval_cpu",
+ "eval_workers",
+ "logs",
+ "master",
+ "rank",
+ "restart",
+ "save",
+ "save_model",
+ "save_state",
+ "show",
+ "workers",
+ "world_size",
+ ])
+ parts = []
+ name_args = dict(args.__dict__)
+ for name, value in name_args.items():
+ if name in ignore_args:
+ continue
+ if value != parser.get_default(name):
+ if isinstance(value, Path):
+ parts.append(f"{name}={value.name}")
+ else:
+ parts.append(f"{name}={value}")
+ if parts:
+ name = " ".join(parts)
+ else:
+ name = "default"
+ return name
diff --git a/demucs/pretrained.py b/demucs/pretrained.py
new file mode 100644
index 0000000000000000000000000000000000000000..6aac5db100cc7a9084af96d2cd083f0c8fac473c
--- /dev/null
+++ b/demucs/pretrained.py
@@ -0,0 +1,107 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+# author: adefossez
+
+import logging
+
+from diffq import DiffQuantizer
+import torch.hub
+
+from .model import Demucs
+from .tasnet import ConvTasNet
+from .utils import set_state
+
+logger = logging.getLogger(__name__)
+ROOT = "https://dl.fbaipublicfiles.com/demucs/v3.0/"
+
+PRETRAINED_MODELS = {
+ 'demucs': 'e07c671f',
+ 'demucs48_hq': '28a1282c',
+ 'demucs_extra': '3646af93',
+ 'demucs_quantized': '07afea75',
+ 'tasnet': 'beb46fac',
+ 'tasnet_extra': 'df3777b2',
+ 'demucs_unittest': '09ebc15f',
+}
+
+SOURCES = ["drums", "bass", "other", "vocals"]
+
+
+def get_url(name):
+ sig = PRETRAINED_MODELS[name]
+ return ROOT + name + "-" + sig[:8] + ".th"
+
+
+def is_pretrained(name):
+ return name in PRETRAINED_MODELS
+
+
+def load_pretrained(name):
+ if name == "demucs":
+ return demucs(pretrained=True)
+ elif name == "demucs48_hq":
+ return demucs(pretrained=True, hq=True, channels=48)
+ elif name == "demucs_extra":
+ return demucs(pretrained=True, extra=True)
+ elif name == "demucs_quantized":
+ return demucs(pretrained=True, quantized=True)
+ elif name == "demucs_unittest":
+ return demucs_unittest(pretrained=True)
+ elif name == "tasnet":
+ return tasnet(pretrained=True)
+ elif name == "tasnet_extra":
+ return tasnet(pretrained=True, extra=True)
+ else:
+ raise ValueError(f"Invalid pretrained name {name}")
+
+
+def _load_state(name, model, quantizer=None):
+ url = get_url(name)
+ state = torch.hub.load_state_dict_from_url(url, map_location='cpu', check_hash=True)
+ set_state(model, quantizer, state)
+ if quantizer:
+ quantizer.detach()
+
+
+def demucs_unittest(pretrained=True):
+ model = Demucs(channels=4, sources=SOURCES)
+ if pretrained:
+ _load_state('demucs_unittest', model)
+ return model
+
+
+def demucs(pretrained=True, extra=False, quantized=False, hq=False, channels=64):
+ if not pretrained and (extra or quantized or hq):
+ raise ValueError("if extra or quantized is True, pretrained must be True.")
+ model = Demucs(sources=SOURCES, channels=channels)
+ if pretrained:
+ name = 'demucs'
+ if channels != 64:
+ name += str(channels)
+ quantizer = None
+ if sum([extra, quantized, hq]) > 1:
+ raise ValueError("Only one of extra, quantized, hq, can be True.")
+ if quantized:
+ quantizer = DiffQuantizer(model, group_size=8, min_size=1)
+ name += '_quantized'
+ if extra:
+ name += '_extra'
+ if hq:
+ name += '_hq'
+ _load_state(name, model, quantizer)
+ return model
+
+
+def tasnet(pretrained=True, extra=False):
+ if not pretrained and extra:
+ raise ValueError("if extra is True, pretrained must be True.")
+ model = ConvTasNet(X=10, sources=SOURCES)
+ if pretrained:
+ name = 'tasnet'
+ if extra:
+ name = 'tasnet_extra'
+ _load_state(name, model)
+ return model
diff --git a/demucs/raw.py b/demucs/raw.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4941ad2d7ed858f490db441f5b46b12bd61ad78
--- /dev/null
+++ b/demucs/raw.py
@@ -0,0 +1,173 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import argparse
+import os
+from collections import defaultdict, namedtuple
+from pathlib import Path
+
+import musdb
+import numpy as np
+import torch as th
+import tqdm
+from torch.utils.data import DataLoader
+
+from .audio import AudioFile
+
+ChunkInfo = namedtuple("ChunkInfo", ["file_index", "offset", "local_index"])
+
+
+class Rawset:
+ """
+ Dataset of raw, normalized, float32 audio files
+ """
+ def __init__(self, path, samples=None, stride=None, channels=2, streams=None):
+ self.path = Path(path)
+ self.channels = channels
+ self.samples = samples
+ if stride is None:
+ stride = samples if samples is not None else 0
+ self.stride = stride
+ entries = defaultdict(list)
+ for root, folders, files in os.walk(self.path, followlinks=True):
+ folders.sort()
+ files.sort()
+ for file in files:
+ if file.endswith(".raw"):
+ path = Path(root) / file
+ name, stream = path.stem.rsplit('.', 1)
+ entries[(path.parent.relative_to(self.path), name)].append(int(stream))
+
+ self._entries = list(entries.keys())
+
+ sizes = []
+ self._lengths = []
+ ref_streams = sorted(entries[self._entries[0]])
+ assert ref_streams == list(range(len(ref_streams)))
+ if streams is None:
+ self.streams = ref_streams
+ else:
+ self.streams = streams
+ for entry in sorted(entries.keys()):
+ streams = entries[entry]
+ assert sorted(streams) == ref_streams
+ file = self._path(*entry)
+ length = file.stat().st_size // (4 * channels)
+ if samples is None:
+ sizes.append(1)
+ else:
+ if length < samples:
+ self._entries.remove(entry)
+ continue
+ sizes.append((length - samples) // stride + 1)
+ self._lengths.append(length)
+ if not sizes:
+ raise ValueError(f"Empty dataset {self.path}")
+ self._cumulative_sizes = np.cumsum(sizes)
+ self._sizes = sizes
+
+ def __len__(self):
+ return self._cumulative_sizes[-1]
+
+ @property
+ def total_length(self):
+ return sum(self._lengths)
+
+ def chunk_info(self, index):
+ file_index = np.searchsorted(self._cumulative_sizes, index, side='right')
+ if file_index == 0:
+ local_index = index
+ else:
+ local_index = index - self._cumulative_sizes[file_index - 1]
+ return ChunkInfo(offset=local_index * self.stride,
+ file_index=file_index,
+ local_index=local_index)
+
+ def _path(self, folder, name, stream=0):
+ return self.path / folder / (name + f'.{stream}.raw')
+
+ def __getitem__(self, index):
+ chunk = self.chunk_info(index)
+ entry = self._entries[chunk.file_index]
+
+ length = self.samples or self._lengths[chunk.file_index]
+ streams = []
+ to_read = length * self.channels * 4
+ for stream_index, stream in enumerate(self.streams):
+ offset = chunk.offset * 4 * self.channels
+ file = open(self._path(*entry, stream=stream), 'rb')
+ file.seek(offset)
+ content = file.read(to_read)
+ assert len(content) == to_read
+ content = np.frombuffer(content, dtype=np.float32)
+ content = content.copy() # make writable
+ streams.append(th.from_numpy(content).view(length, self.channels).t())
+ return th.stack(streams, dim=0)
+
+ def name(self, index):
+ chunk = self.chunk_info(index)
+ folder, name = self._entries[chunk.file_index]
+ return folder / name
+
+
+class MusDBSet:
+ def __init__(self, mus, streams=slice(None), samplerate=44100, channels=2):
+ self.mus = mus
+ self.streams = streams
+ self.samplerate = samplerate
+ self.channels = channels
+
+ def __len__(self):
+ return len(self.mus.tracks)
+
+ def __getitem__(self, index):
+ track = self.mus.tracks[index]
+ return (track.name, AudioFile(track.path).read(channels=self.channels,
+ seek_time=0,
+ streams=self.streams,
+ samplerate=self.samplerate))
+
+
+def build_raw(mus, destination, normalize, workers, samplerate, channels):
+ destination.mkdir(parents=True, exist_ok=True)
+ loader = DataLoader(MusDBSet(mus, channels=channels, samplerate=samplerate),
+ batch_size=1,
+ num_workers=workers,
+ collate_fn=lambda x: x[0])
+ for name, streams in tqdm.tqdm(loader):
+ if normalize:
+ ref = streams[0].mean(dim=0) # use mono mixture as reference
+ streams = (streams - ref.mean()) / ref.std()
+ for index, stream in enumerate(streams):
+ open(destination / (name + f'.{index}.raw'), "wb").write(stream.t().numpy().tobytes())
+
+
+def main():
+ parser = argparse.ArgumentParser('rawset')
+ parser.add_argument('--workers', type=int, default=10)
+ parser.add_argument('--samplerate', type=int, default=44100)
+ parser.add_argument('--channels', type=int, default=2)
+ parser.add_argument('musdb', type=Path)
+ parser.add_argument('destination', type=Path)
+
+ args = parser.parse_args()
+
+ build_raw(musdb.DB(root=args.musdb, subsets=["train"], split="train"),
+ args.destination / "train",
+ normalize=True,
+ channels=args.channels,
+ samplerate=args.samplerate,
+ workers=args.workers)
+ build_raw(musdb.DB(root=args.musdb, subsets=["train"], split="valid"),
+ args.destination / "valid",
+ normalize=True,
+ samplerate=args.samplerate,
+ channels=args.channels,
+ workers=args.workers)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/demucs/repitch.py b/demucs/repitch.py
new file mode 100644
index 0000000000000000000000000000000000000000..8846ab2d951a024c95067f66a113968500442828
--- /dev/null
+++ b/demucs/repitch.py
@@ -0,0 +1,96 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import io
+import random
+import subprocess as sp
+import tempfile
+
+import numpy as np
+import torch
+from scipy.io import wavfile
+
+
+def i16_pcm(wav):
+ if wav.dtype == np.int16:
+ return wav
+ return (wav * 2**15).clamp_(-2**15, 2**15 - 1).short()
+
+
+def f32_pcm(wav):
+ if wav.dtype == np.float:
+ return wav
+ return wav.float() / 2**15
+
+
+class RepitchedWrapper:
+ """
+ Wrap a dataset to apply online change of pitch / tempo.
+ """
+ def __init__(self, dataset, proba=0.2, max_pitch=2, max_tempo=12, tempo_std=5, vocals=[3]):
+ self.dataset = dataset
+ self.proba = proba
+ self.max_pitch = max_pitch
+ self.max_tempo = max_tempo
+ self.tempo_std = tempo_std
+ self.vocals = vocals
+
+ def __len__(self):
+ return len(self.dataset)
+
+ def __getitem__(self, index):
+ streams = self.dataset[index]
+ in_length = streams.shape[-1]
+ out_length = int((1 - 0.01 * self.max_tempo) * in_length)
+
+ if random.random() < self.proba:
+ delta_pitch = random.randint(-self.max_pitch, self.max_pitch)
+ delta_tempo = random.gauss(0, self.tempo_std)
+ delta_tempo = min(max(-self.max_tempo, delta_tempo), self.max_tempo)
+ outs = []
+ for idx, stream in enumerate(streams):
+ stream = repitch(
+ stream,
+ delta_pitch,
+ delta_tempo,
+ voice=idx in self.vocals)
+ outs.append(stream[:, :out_length])
+ streams = torch.stack(outs)
+ else:
+ streams = streams[..., :out_length]
+ return streams
+
+
+def repitch(wav, pitch, tempo, voice=False, quick=False, samplerate=44100):
+ """
+ tempo is a relative delta in percentage, so tempo=10 means tempo at 110%!
+ pitch is in semi tones.
+ Requires `soundstretch` to be installed, see
+ https://www.surina.net/soundtouch/soundstretch.html
+ """
+ outfile = tempfile.NamedTemporaryFile(suffix=".wav")
+ in_ = io.BytesIO()
+ wavfile.write(in_, samplerate, i16_pcm(wav).t().numpy())
+ command = [
+ "soundstretch",
+ "stdin",
+ outfile.name,
+ f"-pitch={pitch}",
+ f"-tempo={tempo:.6f}",
+ ]
+ if quick:
+ command += ["-quick"]
+ if voice:
+ command += ["-speech"]
+ try:
+ sp.run(command, capture_output=True, input=in_.getvalue(), check=True)
+ except sp.CalledProcessError as error:
+ raise RuntimeError(f"Could not change bpm because {error.stderr.decode('utf-8')}")
+ sr, wav = wavfile.read(outfile.name)
+ wav = wav.copy()
+ wav = f32_pcm(torch.from_numpy(wav).t())
+ assert sr == samplerate
+ return wav
diff --git a/demucs/separate.py b/demucs/separate.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fc7af9e711978b3e21398aa6f1deb9ae87dd370
--- /dev/null
+++ b/demucs/separate.py
@@ -0,0 +1,185 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import argparse
+import sys
+from pathlib import Path
+import subprocess
+
+import julius
+import torch as th
+import torchaudio as ta
+
+from .audio import AudioFile, convert_audio_channels
+from .pretrained import is_pretrained, load_pretrained
+from .utils import apply_model, load_model
+
+
+def load_track(track, device, audio_channels, samplerate):
+ errors = {}
+ wav = None
+
+ try:
+ wav = AudioFile(track).read(
+ streams=0,
+ samplerate=samplerate,
+ channels=audio_channels).to(device)
+ except FileNotFoundError:
+ errors['ffmpeg'] = 'Ffmpeg is not installed.'
+ except subprocess.CalledProcessError:
+ errors['ffmpeg'] = 'FFmpeg could not read the file.'
+
+ if wav is None:
+ try:
+ wav, sr = ta.load(str(track))
+ except RuntimeError as err:
+ errors['torchaudio'] = err.args[0]
+ else:
+ wav = convert_audio_channels(wav, audio_channels)
+ wav = wav.to(device)
+ wav = julius.resample_frac(wav, sr, samplerate)
+
+ if wav is None:
+ print(f"Could not load file {track}. "
+ "Maybe it is not a supported file format? ")
+ for backend, error in errors.items():
+ print(f"When trying to load using {backend}, got the following error: {error}")
+ sys.exit(1)
+ return wav
+
+
+def encode_mp3(wav, path, bitrate=320, samplerate=44100, channels=2, verbose=False):
+ try:
+ import lameenc
+ except ImportError:
+ print("Failed to call lame encoder. Maybe it is not installed? "
+ "On windows, run `python.exe -m pip install -U lameenc`, "
+ "on OSX/Linux, run `python3 -m pip install -U lameenc`, "
+ "then try again.", file=sys.stderr)
+ sys.exit(1)
+ encoder = lameenc.Encoder()
+ encoder.set_bit_rate(bitrate)
+ encoder.set_in_sample_rate(samplerate)
+ encoder.set_channels(channels)
+ encoder.set_quality(2) # 2-highest, 7-fastest
+ if not verbose:
+ encoder.silence()
+ wav = wav.transpose(0, 1).numpy()
+ mp3_data = encoder.encode(wav.tobytes())
+ mp3_data += encoder.flush()
+ with open(path, "wb") as f:
+ f.write(mp3_data)
+
+
+def main():
+ parser = argparse.ArgumentParser("demucs.separate",
+ description="Separate the sources for the given tracks")
+ parser.add_argument("tracks", nargs='+', type=Path, default=[], help='Path to tracks')
+ parser.add_argument("-n",
+ "--name",
+ default="demucs_quantized",
+ help="Model name. See README.md for the list of pretrained models. "
+ "Default is demucs_quantized.")
+ parser.add_argument("-v", "--verbose", action="store_true")
+ parser.add_argument("-o",
+ "--out",
+ type=Path,
+ default=Path("separated"),
+ help="Folder where to put extracted tracks. A subfolder "
+ "with the model name will be created.")
+ parser.add_argument("--models",
+ type=Path,
+ default=Path("models"),
+ help="Path to trained models. "
+ "Also used to store downloaded pretrained models")
+ parser.add_argument("-d",
+ "--device",
+ default="cuda" if th.cuda.is_available() else "cpu",
+ help="Device to use, default is cuda if available else cpu")
+ parser.add_argument("--shifts",
+ default=0,
+ type=int,
+ help="Number of random shifts for equivariant stabilization."
+ "Increase separation time but improves quality for Demucs. 10 was used "
+ "in the original paper.")
+ parser.add_argument("--overlap",
+ default=0.25,
+ type=float,
+ help="Overlap between the splits.")
+ parser.add_argument("--no-split",
+ action="store_false",
+ dest="split",
+ default=True,
+ help="Doesn't split audio in chunks. This can use large amounts of memory.")
+ parser.add_argument("--float32",
+ action="store_true",
+ help="Convert the output wavefile to use pcm f32 format instead of s16. "
+ "This should not make a difference if you just plan on listening to the "
+ "audio but might be needed to compute exactly metrics like SDR etc.")
+ parser.add_argument("--int16",
+ action="store_false",
+ dest="float32",
+ help="Opposite of --float32, here for compatibility.")
+ parser.add_argument("--mp3", action="store_true",
+ help="Convert the output wavs to mp3.")
+ parser.add_argument("--mp3-bitrate",
+ default=320,
+ type=int,
+ help="Bitrate of converted mp3.")
+
+ args = parser.parse_args()
+ name = args.name + ".th"
+ model_path = args.models / name
+ if model_path.is_file():
+ model = load_model(model_path)
+ else:
+ if is_pretrained(args.name):
+ model = load_pretrained(args.name)
+ else:
+ print(f"No pre-trained model {args.name}", file=sys.stderr)
+ sys.exit(1)
+ model.to(args.device)
+
+ out = args.out / args.name
+ out.mkdir(parents=True, exist_ok=True)
+ print(f"Separated tracks will be stored in {out.resolve()}")
+ for track in args.tracks:
+ if not track.exists():
+ print(
+ f"File {track} does not exist. If the path contains spaces, "
+ "please try again after surrounding the entire path with quotes \"\".",
+ file=sys.stderr)
+ continue
+ print(f"Separating track {track}")
+ wav = load_track(track, args.device, model.audio_channels, model.samplerate)
+
+ ref = wav.mean(0)
+ wav = (wav - ref.mean()) / ref.std()
+ sources = apply_model(model, wav, shifts=args.shifts, split=args.split,
+ overlap=args.overlap, progress=True)
+ sources = sources * ref.std() + ref.mean()
+
+ track_folder = out / track.name.rsplit(".", 1)[0]
+ track_folder.mkdir(exist_ok=True)
+ for source, name in zip(sources, model.sources):
+ source = source / max(1.01 * source.abs().max(), 1)
+ if args.mp3 or not args.float32:
+ source = (source * 2**15).clamp_(-2**15, 2**15 - 1).short()
+ source = source.cpu()
+ stem = str(track_folder / name)
+ if args.mp3:
+ encode_mp3(source, stem + ".mp3",
+ bitrate=args.mp3_bitrate,
+ samplerate=model.samplerate,
+ channels=model.audio_channels,
+ verbose=args.verbose)
+ else:
+ wavname = str(track_folder / f"{name}.wav")
+ ta.save(wavname, source, sample_rate=model.samplerate)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/demucs/tasnet.py b/demucs/tasnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecc1257925ea8f4fbe389ddd6d73ce9fdf45f6d4
--- /dev/null
+++ b/demucs/tasnet.py
@@ -0,0 +1,452 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# Created on 2018/12
+# Author: Kaituo XU
+# Modified on 2019/11 by Alexandre Defossez, added support for multiple output channels
+# Here is the original license:
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Kaituo XU
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import math
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from .utils import capture_init
+
+EPS = 1e-8
+
+
+def overlap_and_add(signal, frame_step):
+ outer_dimensions = signal.size()[:-2]
+ frames, frame_length = signal.size()[-2:]
+
+ subframe_length = math.gcd(frame_length, frame_step) # gcd=Greatest Common Divisor
+ subframe_step = frame_step // subframe_length
+ subframes_per_frame = frame_length // subframe_length
+ output_size = frame_step * (frames - 1) + frame_length
+ output_subframes = output_size // subframe_length
+
+ subframe_signal = signal.view(*outer_dimensions, -1, subframe_length)
+
+ frame = torch.arange(0, output_subframes,
+ device=signal.device).unfold(0, subframes_per_frame, subframe_step)
+ frame = frame.long() # signal may in GPU or CPU
+ frame = frame.contiguous().view(-1)
+
+ result = signal.new_zeros(*outer_dimensions, output_subframes, subframe_length)
+ result.index_add_(-2, frame, subframe_signal)
+ result = result.view(*outer_dimensions, -1)
+ return result
+
+
+class ConvTasNet(nn.Module):
+ @capture_init
+ def __init__(self,
+ sources,
+ N=256,
+ L=20,
+ B=256,
+ H=512,
+ P=3,
+ X=8,
+ R=4,
+ audio_channels=2,
+ norm_type="gLN",
+ causal=False,
+ mask_nonlinear='relu',
+ samplerate=44100,
+ segment_length=44100 * 2 * 4):
+ """
+ Args:
+ sources: list of sources
+ N: Number of filters in autoencoder
+ L: Length of the filters (in samples)
+ B: Number of channels in bottleneck 1 × 1-conv block
+ H: Number of channels in convolutional blocks
+ P: Kernel size in convolutional blocks
+ X: Number of convolutional blocks in each repeat
+ R: Number of repeats
+ norm_type: BN, gLN, cLN
+ causal: causal or non-causal
+ mask_nonlinear: use which non-linear function to generate mask
+ """
+ super(ConvTasNet, self).__init__()
+ # Hyper-parameter
+ self.sources = sources
+ self.C = len(sources)
+ self.N, self.L, self.B, self.H, self.P, self.X, self.R = N, L, B, H, P, X, R
+ self.norm_type = norm_type
+ self.causal = causal
+ self.mask_nonlinear = mask_nonlinear
+ self.audio_channels = audio_channels
+ self.samplerate = samplerate
+ self.segment_length = segment_length
+ # Components
+ self.encoder = Encoder(L, N, audio_channels)
+ self.separator = TemporalConvNet(
+ N, B, H, P, X, R, self.C, norm_type, causal, mask_nonlinear)
+ self.decoder = Decoder(N, L, audio_channels)
+ # init
+ for p in self.parameters():
+ if p.dim() > 1:
+ nn.init.xavier_normal_(p)
+
+ def valid_length(self, length):
+ return length
+
+ def forward(self, mixture):
+ """
+ Args:
+ mixture: [M, T], M is batch size, T is #samples
+ Returns:
+ est_source: [M, C, T]
+ """
+ mixture_w = self.encoder(mixture)
+ est_mask = self.separator(mixture_w)
+ est_source = self.decoder(mixture_w, est_mask)
+
+ # T changed after conv1d in encoder, fix it here
+ T_origin = mixture.size(-1)
+ T_conv = est_source.size(-1)
+ est_source = F.pad(est_source, (0, T_origin - T_conv))
+ return est_source
+
+
+class Encoder(nn.Module):
+ """Estimation of the nonnegative mixture weight by a 1-D conv layer.
+ """
+ def __init__(self, L, N, audio_channels):
+ super(Encoder, self).__init__()
+ # Hyper-parameter
+ self.L, self.N = L, N
+ # Components
+ # 50% overlap
+ self.conv1d_U = nn.Conv1d(audio_channels, N, kernel_size=L, stride=L // 2, bias=False)
+
+ def forward(self, mixture):
+ """
+ Args:
+ mixture: [M, T], M is batch size, T is #samples
+ Returns:
+ mixture_w: [M, N, K], where K = (T-L)/(L/2)+1 = 2T/L-1
+ """
+ mixture_w = F.relu(self.conv1d_U(mixture)) # [M, N, K]
+ return mixture_w
+
+
+class Decoder(nn.Module):
+ def __init__(self, N, L, audio_channels):
+ super(Decoder, self).__init__()
+ # Hyper-parameter
+ self.N, self.L = N, L
+ self.audio_channels = audio_channels
+ # Components
+ self.basis_signals = nn.Linear(N, audio_channels * L, bias=False)
+
+ def forward(self, mixture_w, est_mask):
+ """
+ Args:
+ mixture_w: [M, N, K]
+ est_mask: [M, C, N, K]
+ Returns:
+ est_source: [M, C, T]
+ """
+ # D = W * M
+ source_w = torch.unsqueeze(mixture_w, 1) * est_mask # [M, C, N, K]
+ source_w = torch.transpose(source_w, 2, 3) # [M, C, K, N]
+ # S = DV
+ est_source = self.basis_signals(source_w) # [M, C, K, ac * L]
+ m, c, k, _ = est_source.size()
+ est_source = est_source.view(m, c, k, self.audio_channels, -1).transpose(2, 3).contiguous()
+ est_source = overlap_and_add(est_source, self.L // 2) # M x C x ac x T
+ return est_source
+
+
+class TemporalConvNet(nn.Module):
+ def __init__(self, N, B, H, P, X, R, C, norm_type="gLN", causal=False, mask_nonlinear='relu'):
+ """
+ Args:
+ N: Number of filters in autoencoder
+ B: Number of channels in bottleneck 1 × 1-conv block
+ H: Number of channels in convolutional blocks
+ P: Kernel size in convolutional blocks
+ X: Number of convolutional blocks in each repeat
+ R: Number of repeats
+ C: Number of speakers
+ norm_type: BN, gLN, cLN
+ causal: causal or non-causal
+ mask_nonlinear: use which non-linear function to generate mask
+ """
+ super(TemporalConvNet, self).__init__()
+ # Hyper-parameter
+ self.C = C
+ self.mask_nonlinear = mask_nonlinear
+ # Components
+ # [M, N, K] -> [M, N, K]
+ layer_norm = ChannelwiseLayerNorm(N)
+ # [M, N, K] -> [M, B, K]
+ bottleneck_conv1x1 = nn.Conv1d(N, B, 1, bias=False)
+ # [M, B, K] -> [M, B, K]
+ repeats = []
+ for r in range(R):
+ blocks = []
+ for x in range(X):
+ dilation = 2**x
+ padding = (P - 1) * dilation if causal else (P - 1) * dilation // 2
+ blocks += [
+ TemporalBlock(B,
+ H,
+ P,
+ stride=1,
+ padding=padding,
+ dilation=dilation,
+ norm_type=norm_type,
+ causal=causal)
+ ]
+ repeats += [nn.Sequential(*blocks)]
+ temporal_conv_net = nn.Sequential(*repeats)
+ # [M, B, K] -> [M, C*N, K]
+ mask_conv1x1 = nn.Conv1d(B, C * N, 1, bias=False)
+ # Put together
+ self.network = nn.Sequential(layer_norm, bottleneck_conv1x1, temporal_conv_net,
+ mask_conv1x1)
+
+ def forward(self, mixture_w):
+ """
+ Keep this API same with TasNet
+ Args:
+ mixture_w: [M, N, K], M is batch size
+ returns:
+ est_mask: [M, C, N, K]
+ """
+ M, N, K = mixture_w.size()
+ score = self.network(mixture_w) # [M, N, K] -> [M, C*N, K]
+ score = score.view(M, self.C, N, K) # [M, C*N, K] -> [M, C, N, K]
+ if self.mask_nonlinear == 'softmax':
+ est_mask = F.softmax(score, dim=1)
+ elif self.mask_nonlinear == 'relu':
+ est_mask = F.relu(score)
+ else:
+ raise ValueError("Unsupported mask non-linear function")
+ return est_mask
+
+
+class TemporalBlock(nn.Module):
+ def __init__(self,
+ in_channels,
+ out_channels,
+ kernel_size,
+ stride,
+ padding,
+ dilation,
+ norm_type="gLN",
+ causal=False):
+ super(TemporalBlock, self).__init__()
+ # [M, B, K] -> [M, H, K]
+ conv1x1 = nn.Conv1d(in_channels, out_channels, 1, bias=False)
+ prelu = nn.PReLU()
+ norm = chose_norm(norm_type, out_channels)
+ # [M, H, K] -> [M, B, K]
+ dsconv = DepthwiseSeparableConv(out_channels, in_channels, kernel_size, stride, padding,
+ dilation, norm_type, causal)
+ # Put together
+ self.net = nn.Sequential(conv1x1, prelu, norm, dsconv)
+
+ def forward(self, x):
+ """
+ Args:
+ x: [M, B, K]
+ Returns:
+ [M, B, K]
+ """
+ residual = x
+ out = self.net(x)
+ # TODO: when P = 3 here works fine, but when P = 2 maybe need to pad?
+ return out + residual # look like w/o F.relu is better than w/ F.relu
+ # return F.relu(out + residual)
+
+
+class DepthwiseSeparableConv(nn.Module):
+ def __init__(self,
+ in_channels,
+ out_channels,
+ kernel_size,
+ stride,
+ padding,
+ dilation,
+ norm_type="gLN",
+ causal=False):
+ super(DepthwiseSeparableConv, self).__init__()
+ # Use `groups` option to implement depthwise convolution
+ # [M, H, K] -> [M, H, K]
+ depthwise_conv = nn.Conv1d(in_channels,
+ in_channels,
+ kernel_size,
+ stride=stride,
+ padding=padding,
+ dilation=dilation,
+ groups=in_channels,
+ bias=False)
+ if causal:
+ chomp = Chomp1d(padding)
+ prelu = nn.PReLU()
+ norm = chose_norm(norm_type, in_channels)
+ # [M, H, K] -> [M, B, K]
+ pointwise_conv = nn.Conv1d(in_channels, out_channels, 1, bias=False)
+ # Put together
+ if causal:
+ self.net = nn.Sequential(depthwise_conv, chomp, prelu, norm, pointwise_conv)
+ else:
+ self.net = nn.Sequential(depthwise_conv, prelu, norm, pointwise_conv)
+
+ def forward(self, x):
+ """
+ Args:
+ x: [M, H, K]
+ Returns:
+ result: [M, B, K]
+ """
+ return self.net(x)
+
+
+class Chomp1d(nn.Module):
+ """To ensure the output length is the same as the input.
+ """
+ def __init__(self, chomp_size):
+ super(Chomp1d, self).__init__()
+ self.chomp_size = chomp_size
+
+ def forward(self, x):
+ """
+ Args:
+ x: [M, H, Kpad]
+ Returns:
+ [M, H, K]
+ """
+ return x[:, :, :-self.chomp_size].contiguous()
+
+
+def chose_norm(norm_type, channel_size):
+ """The input of normlization will be (M, C, K), where M is batch size,
+ C is channel size and K is sequence length.
+ """
+ if norm_type == "gLN":
+ return GlobalLayerNorm(channel_size)
+ elif norm_type == "cLN":
+ return ChannelwiseLayerNorm(channel_size)
+ elif norm_type == "id":
+ return nn.Identity()
+ else: # norm_type == "BN":
+ # Given input (M, C, K), nn.BatchNorm1d(C) will accumulate statics
+ # along M and K, so this BN usage is right.
+ return nn.BatchNorm1d(channel_size)
+
+
+# TODO: Use nn.LayerNorm to impl cLN to speed up
+class ChannelwiseLayerNorm(nn.Module):
+ """Channel-wise Layer Normalization (cLN)"""
+ def __init__(self, channel_size):
+ super(ChannelwiseLayerNorm, self).__init__()
+ self.gamma = nn.Parameter(torch.Tensor(1, channel_size, 1)) # [1, N, 1]
+ self.beta = nn.Parameter(torch.Tensor(1, channel_size, 1)) # [1, N, 1]
+ self.reset_parameters()
+
+ def reset_parameters(self):
+ self.gamma.data.fill_(1)
+ self.beta.data.zero_()
+
+ def forward(self, y):
+ """
+ Args:
+ y: [M, N, K], M is batch size, N is channel size, K is length
+ Returns:
+ cLN_y: [M, N, K]
+ """
+ mean = torch.mean(y, dim=1, keepdim=True) # [M, 1, K]
+ var = torch.var(y, dim=1, keepdim=True, unbiased=False) # [M, 1, K]
+ cLN_y = self.gamma * (y - mean) / torch.pow(var + EPS, 0.5) + self.beta
+ return cLN_y
+
+
+class GlobalLayerNorm(nn.Module):
+ """Global Layer Normalization (gLN)"""
+ def __init__(self, channel_size):
+ super(GlobalLayerNorm, self).__init__()
+ self.gamma = nn.Parameter(torch.Tensor(1, channel_size, 1)) # [1, N, 1]
+ self.beta = nn.Parameter(torch.Tensor(1, channel_size, 1)) # [1, N, 1]
+ self.reset_parameters()
+
+ def reset_parameters(self):
+ self.gamma.data.fill_(1)
+ self.beta.data.zero_()
+
+ def forward(self, y):
+ """
+ Args:
+ y: [M, N, K], M is batch size, N is channel size, K is length
+ Returns:
+ gLN_y: [M, N, K]
+ """
+ # TODO: in torch 1.0, torch.mean() support dim list
+ mean = y.mean(dim=1, keepdim=True).mean(dim=2, keepdim=True) # [M, 1, 1]
+ var = (torch.pow(y - mean, 2)).mean(dim=1, keepdim=True).mean(dim=2, keepdim=True)
+ gLN_y = self.gamma * (y - mean) / torch.pow(var + EPS, 0.5) + self.beta
+ return gLN_y
+
+
+if __name__ == "__main__":
+ torch.manual_seed(123)
+ M, N, L, T = 2, 3, 4, 12
+ K = 2 * T // L - 1
+ B, H, P, X, R, C, norm_type, causal = 2, 3, 3, 3, 2, 2, "gLN", False
+ mixture = torch.randint(3, (M, T))
+ # test Encoder
+ encoder = Encoder(L, N)
+ encoder.conv1d_U.weight.data = torch.randint(2, encoder.conv1d_U.weight.size())
+ mixture_w = encoder(mixture)
+ print('mixture', mixture)
+ print('U', encoder.conv1d_U.weight)
+ print('mixture_w', mixture_w)
+ print('mixture_w size', mixture_w.size())
+
+ # test TemporalConvNet
+ separator = TemporalConvNet(N, B, H, P, X, R, C, norm_type=norm_type, causal=causal)
+ est_mask = separator(mixture_w)
+ print('est_mask', est_mask)
+
+ # test Decoder
+ decoder = Decoder(N, L)
+ est_mask = torch.randint(2, (B, K, C, N))
+ est_source = decoder(mixture_w, est_mask)
+ print('est_source', est_source)
+
+ # test Conv-TasNet
+ conv_tasnet = ConvTasNet(N, L, B, H, P, X, R, C, norm_type=norm_type)
+ est_source = conv_tasnet(mixture)
+ print('est_source', est_source)
+ print('est_source size', est_source.size())
diff --git a/demucs/test.py b/demucs/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..4140914ddbff3543b4056ca0cb1b5e887434a40a
--- /dev/null
+++ b/demucs/test.py
@@ -0,0 +1,109 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import gzip
+import sys
+from concurrent import futures
+
+import musdb
+import museval
+import torch as th
+import tqdm
+from scipy.io import wavfile
+from torch import distributed
+
+from .audio import convert_audio
+from .utils import apply_model
+
+
+def evaluate(model,
+ musdb_path,
+ eval_folder,
+ workers=2,
+ device="cpu",
+ rank=0,
+ save=False,
+ shifts=0,
+ split=False,
+ overlap=0.25,
+ is_wav=False,
+ world_size=1):
+ """
+ Evaluate model using museval. Run the model
+ on a single GPU, the bottleneck being the call to museval.
+ """
+
+ output_dir = eval_folder / "results"
+ output_dir.mkdir(exist_ok=True, parents=True)
+ json_folder = eval_folder / "results/test"
+ json_folder.mkdir(exist_ok=True, parents=True)
+
+ # we load tracks from the original musdb set
+ test_set = musdb.DB(musdb_path, subsets=["test"], is_wav=is_wav)
+ src_rate = 44100 # hardcoded for now...
+
+ for p in model.parameters():
+ p.requires_grad = False
+ p.grad = None
+
+ pendings = []
+ with futures.ProcessPoolExecutor(workers or 1) as pool:
+ for index in tqdm.tqdm(range(rank, len(test_set), world_size), file=sys.stdout):
+ track = test_set.tracks[index]
+
+ out = json_folder / f"{track.name}.json.gz"
+ if out.exists():
+ continue
+
+ mix = th.from_numpy(track.audio).t().float()
+ ref = mix.mean(dim=0) # mono mixture
+ mix = (mix - ref.mean()) / ref.std()
+ mix = convert_audio(mix, src_rate, model.samplerate, model.audio_channels)
+ estimates = apply_model(model, mix.to(device),
+ shifts=shifts, split=split, overlap=overlap)
+ estimates = estimates * ref.std() + ref.mean()
+
+ estimates = estimates.transpose(1, 2)
+ references = th.stack(
+ [th.from_numpy(track.targets[name].audio).t() for name in model.sources])
+ references = convert_audio(references, src_rate,
+ model.samplerate, model.audio_channels)
+ references = references.transpose(1, 2).numpy()
+ estimates = estimates.cpu().numpy()
+ win = int(1. * model.samplerate)
+ hop = int(1. * model.samplerate)
+ if save:
+ folder = eval_folder / "wav/test" / track.name
+ folder.mkdir(exist_ok=True, parents=True)
+ for name, estimate in zip(model.sources, estimates):
+ wavfile.write(str(folder / (name + ".wav")), 44100, estimate)
+
+ if workers:
+ pendings.append((track.name, pool.submit(
+ museval.evaluate, references, estimates, win=win, hop=hop)))
+ else:
+ pendings.append((track.name, museval.evaluate(
+ references, estimates, win=win, hop=hop)))
+ del references, mix, estimates, track
+
+ for track_name, pending in tqdm.tqdm(pendings, file=sys.stdout):
+ if workers:
+ pending = pending.result()
+ sdr, isr, sir, sar = pending
+ track_store = museval.TrackStore(win=44100, hop=44100, track_name=track_name)
+ for idx, target in enumerate(model.sources):
+ values = {
+ "SDR": sdr[idx].tolist(),
+ "SIR": sir[idx].tolist(),
+ "ISR": isr[idx].tolist(),
+ "SAR": sar[idx].tolist()
+ }
+
+ track_store.add_target(target_name=target, values=values)
+ json_path = json_folder / f"{track_name}.json.gz"
+ gzip.open(json_path, "w").write(track_store.json.encode('utf-8'))
+ if world_size > 1:
+ distributed.barrier()
diff --git a/demucs/train.py b/demucs/train.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bd221279dc986a6df1a8d7b4d4444bb822a1cb3
--- /dev/null
+++ b/demucs/train.py
@@ -0,0 +1,127 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import sys
+
+import tqdm
+from torch.utils.data import DataLoader
+from torch.utils.data.distributed import DistributedSampler
+
+from .utils import apply_model, average_metric, center_trim
+
+
+def train_model(epoch,
+ dataset,
+ model,
+ criterion,
+ optimizer,
+ augment,
+ quantizer=None,
+ diffq=0,
+ repeat=1,
+ device="cpu",
+ seed=None,
+ workers=4,
+ world_size=1,
+ batch_size=16):
+
+ if world_size > 1:
+ sampler = DistributedSampler(dataset)
+ sampler_epoch = epoch * repeat
+ if seed is not None:
+ sampler_epoch += seed * 1000
+ sampler.set_epoch(sampler_epoch)
+ batch_size //= world_size
+ loader = DataLoader(dataset, batch_size=batch_size, sampler=sampler, num_workers=workers)
+ else:
+ loader = DataLoader(dataset, batch_size=batch_size, num_workers=workers, shuffle=True)
+ current_loss = 0
+ model_size = 0
+ for repetition in range(repeat):
+ tq = tqdm.tqdm(loader,
+ ncols=120,
+ desc=f"[{epoch:03d}] train ({repetition + 1}/{repeat})",
+ leave=False,
+ file=sys.stdout,
+ unit=" batch")
+ total_loss = 0
+ for idx, sources in enumerate(tq):
+ if len(sources) < batch_size:
+ # skip uncomplete batch for augment.Remix to work properly
+ continue
+ sources = sources.to(device)
+ sources = augment(sources)
+ mix = sources.sum(dim=1)
+
+ estimates = model(mix)
+ sources = center_trim(sources, estimates)
+ loss = criterion(estimates, sources)
+ model_size = 0
+ if quantizer is not None:
+ model_size = quantizer.model_size()
+
+ train_loss = loss + diffq * model_size
+ train_loss.backward()
+ grad_norm = 0
+ for p in model.parameters():
+ if p.grad is not None:
+ grad_norm += p.grad.data.norm()**2
+ grad_norm = grad_norm**0.5
+ optimizer.step()
+ optimizer.zero_grad()
+
+ if quantizer is not None:
+ model_size = model_size.item()
+
+ total_loss += loss.item()
+ current_loss = total_loss / (1 + idx)
+ tq.set_postfix(loss=f"{current_loss:.4f}", ms=f"{model_size:.2f}",
+ grad=f"{grad_norm:.5f}")
+
+ # free some space before next round
+ del sources, mix, estimates, loss, train_loss
+
+ if world_size > 1:
+ sampler.epoch += 1
+
+ if world_size > 1:
+ current_loss = average_metric(current_loss)
+ return current_loss, model_size
+
+
+def validate_model(epoch,
+ dataset,
+ model,
+ criterion,
+ device="cpu",
+ rank=0,
+ world_size=1,
+ shifts=0,
+ overlap=0.25,
+ split=False):
+ indexes = range(rank, len(dataset), world_size)
+ tq = tqdm.tqdm(indexes,
+ ncols=120,
+ desc=f"[{epoch:03d}] valid",
+ leave=False,
+ file=sys.stdout,
+ unit=" track")
+ current_loss = 0
+ for index in tq:
+ streams = dataset[index]
+ # first five minutes to avoid OOM on --upsample models
+ streams = streams[..., :15_000_000]
+ streams = streams.to(device)
+ sources = streams[1:]
+ mix = streams[0]
+ estimates = apply_model(model, mix, shifts=shifts, split=split, overlap=overlap)
+ loss = criterion(estimates, sources)
+ current_loss += loss.item() / len(indexes)
+ del estimates, streams, sources
+
+ if world_size > 1:
+ current_loss = average_metric(current_loss, len(indexes))
+ return current_loss
diff --git a/demucs/utils.py b/demucs/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..4364184059b1afe3c8379c77793a8e76dccf9699
--- /dev/null
+++ b/demucs/utils.py
@@ -0,0 +1,323 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import errno
+import functools
+import hashlib
+import inspect
+import io
+import os
+import random
+import socket
+import tempfile
+import warnings
+import zlib
+from contextlib import contextmanager
+
+from diffq import UniformQuantizer, DiffQuantizer
+import torch as th
+import tqdm
+from torch import distributed
+from torch.nn import functional as F
+
+
+def center_trim(tensor, reference):
+ """
+ Center trim `tensor` with respect to `reference`, along the last dimension.
+ `reference` can also be a number, representing the length to trim to.
+ If the size difference != 0 mod 2, the extra sample is removed on the right side.
+ """
+ if hasattr(reference, "size"):
+ reference = reference.size(-1)
+ delta = tensor.size(-1) - reference
+ if delta < 0:
+ raise ValueError("tensor must be larger than reference. " f"Delta is {delta}.")
+ if delta:
+ tensor = tensor[..., delta // 2:-(delta - delta // 2)]
+ return tensor
+
+
+def average_metric(metric, count=1.):
+ """
+ Average `metric` which should be a float across all hosts. `count` should be
+ the weight for this particular host (i.e. number of examples).
+ """
+ metric = th.tensor([count, count * metric], dtype=th.float32, device='cuda')
+ distributed.all_reduce(metric, op=distributed.ReduceOp.SUM)
+ return metric[1].item() / metric[0].item()
+
+
+def free_port(host='', low=20000, high=40000):
+ """
+ Return a port number that is most likely free.
+ This could suffer from a race condition although
+ it should be quite rare.
+ """
+ sock = socket.socket()
+ while True:
+ port = random.randint(low, high)
+ try:
+ sock.bind((host, port))
+ except OSError as error:
+ if error.errno == errno.EADDRINUSE:
+ continue
+ raise
+ return port
+
+
+def sizeof_fmt(num, suffix='B'):
+ """
+ Given `num` bytes, return human readable size.
+ Taken from https://stackoverflow.com/a/1094933
+ """
+ for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
+ if abs(num) < 1024.0:
+ return "%3.1f%s%s" % (num, unit, suffix)
+ num /= 1024.0
+ return "%.1f%s%s" % (num, 'Yi', suffix)
+
+
+def human_seconds(seconds, display='.2f'):
+ """
+ Given `seconds` seconds, return human readable duration.
+ """
+ value = seconds * 1e6
+ ratios = [1e3, 1e3, 60, 60, 24]
+ names = ['us', 'ms', 's', 'min', 'hrs', 'days']
+ last = names.pop(0)
+ for name, ratio in zip(names, ratios):
+ if value / ratio < 0.3:
+ break
+ value /= ratio
+ last = name
+ return f"{format(value, display)} {last}"
+
+
+class TensorChunk:
+ def __init__(self, tensor, offset=0, length=None):
+ total_length = tensor.shape[-1]
+ assert offset >= 0
+ assert offset < total_length
+
+ if length is None:
+ length = total_length - offset
+ else:
+ length = min(total_length - offset, length)
+
+ self.tensor = tensor
+ self.offset = offset
+ self.length = length
+ self.device = tensor.device
+
+ @property
+ def shape(self):
+ shape = list(self.tensor.shape)
+ shape[-1] = self.length
+ return shape
+
+ def padded(self, target_length):
+ delta = target_length - self.length
+ total_length = self.tensor.shape[-1]
+ assert delta >= 0
+
+ start = self.offset - delta // 2
+ end = start + target_length
+
+ correct_start = max(0, start)
+ correct_end = min(total_length, end)
+
+ pad_left = correct_start - start
+ pad_right = end - correct_end
+
+ out = F.pad(self.tensor[..., correct_start:correct_end], (pad_left, pad_right))
+ assert out.shape[-1] == target_length
+ return out
+
+
+def tensor_chunk(tensor_or_chunk):
+ if isinstance(tensor_or_chunk, TensorChunk):
+ return tensor_or_chunk
+ else:
+ assert isinstance(tensor_or_chunk, th.Tensor)
+ return TensorChunk(tensor_or_chunk)
+
+
+def apply_model(model, mix, shifts=None, split=False,
+ overlap=0.25, transition_power=1., progress=False):
+ """
+ Apply model to a given mixture.
+
+ Args:
+ shifts (int): if > 0, will shift in time `mix` by a random amount between 0 and 0.5 sec
+ and apply the oppositve shift to the output. This is repeated `shifts` time and
+ all predictions are averaged. This effectively makes the model time equivariant
+ and improves SDR by up to 0.2 points.
+ split (bool): if True, the input will be broken down in 8 seconds extracts
+ and predictions will be performed individually on each and concatenated.
+ Useful for model with large memory footprint like Tasnet.
+ progress (bool): if True, show a progress bar (requires split=True)
+ """
+ assert transition_power >= 1, "transition_power < 1 leads to weird behavior."
+ device = mix.device
+ channels, length = mix.shape
+ if split:
+ out = th.zeros(len(model.sources), channels, length, device=device)
+ sum_weight = th.zeros(length, device=device)
+ segment = model.segment_length
+ stride = int((1 - overlap) * segment)
+ offsets = range(0, length, stride)
+ scale = stride / model.samplerate
+ if progress:
+ offsets = tqdm.tqdm(offsets, unit_scale=scale, ncols=120, unit='seconds')
+ # We start from a triangle shaped weight, with maximal weight in the middle
+ # of the segment. Then we normalize and take to the power `transition_power`.
+ # Large values of transition power will lead to sharper transitions.
+ weight = th.cat([th.arange(1, segment // 2 + 1),
+ th.arange(segment - segment // 2, 0, -1)]).to(device)
+ assert len(weight) == segment
+ # If the overlap < 50%, this will translate to linear transition when
+ # transition_power is 1.
+ weight = (weight / weight.max())**transition_power
+ for offset in offsets:
+ chunk = TensorChunk(mix, offset, segment)
+ chunk_out = apply_model(model, chunk, shifts=shifts)
+ chunk_length = chunk_out.shape[-1]
+ out[..., offset:offset + segment] += weight[:chunk_length] * chunk_out
+ sum_weight[offset:offset + segment] += weight[:chunk_length]
+ offset += segment
+ assert sum_weight.min() > 0
+ out /= sum_weight
+ return out
+ elif shifts:
+ max_shift = int(0.5 * model.samplerate)
+ mix = tensor_chunk(mix)
+ padded_mix = mix.padded(length + 2 * max_shift)
+ out = 0
+ for _ in range(shifts):
+ offset = random.randint(0, max_shift)
+ shifted = TensorChunk(padded_mix, offset, length + max_shift - offset)
+ shifted_out = apply_model(model, shifted)
+ out += shifted_out[..., max_shift - offset:]
+ out /= shifts
+ return out
+ else:
+ valid_length = model.valid_length(length)
+ mix = tensor_chunk(mix)
+ padded_mix = mix.padded(valid_length)
+ with th.no_grad():
+ out = model(padded_mix.unsqueeze(0))[0]
+ return center_trim(out, length)
+
+
+@contextmanager
+def temp_filenames(count, delete=True):
+ names = []
+ try:
+ for _ in range(count):
+ names.append(tempfile.NamedTemporaryFile(delete=False).name)
+ yield names
+ finally:
+ if delete:
+ for name in names:
+ os.unlink(name)
+
+
+def get_quantizer(model, args, optimizer=None):
+ quantizer = None
+ if args.diffq:
+ quantizer = DiffQuantizer(
+ model, min_size=args.q_min_size, group_size=8)
+ if optimizer is not None:
+ quantizer.setup_optimizer(optimizer)
+ elif args.qat:
+ quantizer = UniformQuantizer(
+ model, bits=args.qat, min_size=args.q_min_size)
+ return quantizer
+
+
+def load_model(path, strict=False):
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ load_from = path
+ package = th.load(load_from, 'cpu')
+
+ klass = package["klass"]
+ args = package["args"]
+ kwargs = package["kwargs"]
+
+ if strict:
+ model = klass(*args, **kwargs)
+ else:
+ sig = inspect.signature(klass)
+ for key in list(kwargs):
+ if key not in sig.parameters:
+ warnings.warn("Dropping inexistant parameter " + key)
+ del kwargs[key]
+ model = klass(*args, **kwargs)
+
+ state = package["state"]
+ training_args = package["training_args"]
+ quantizer = get_quantizer(model, training_args)
+
+ set_state(model, quantizer, state)
+ return model
+
+
+def get_state(model, quantizer):
+ if quantizer is None:
+ state = {k: p.data.to('cpu') for k, p in model.state_dict().items()}
+ else:
+ state = quantizer.get_quantized_state()
+ buf = io.BytesIO()
+ th.save(state, buf)
+ state = {'compressed': zlib.compress(buf.getvalue())}
+ return state
+
+
+def set_state(model, quantizer, state):
+ if quantizer is None:
+ model.load_state_dict(state)
+ else:
+ buf = io.BytesIO(zlib.decompress(state["compressed"]))
+ state = th.load(buf, "cpu")
+ quantizer.restore_quantized_state(state)
+
+ return state
+
+
+def save_state(state, path):
+ buf = io.BytesIO()
+ th.save(state, buf)
+ sig = hashlib.sha256(buf.getvalue()).hexdigest()[:8]
+
+ path = path.parent / (path.stem + "-" + sig + path.suffix)
+ path.write_bytes(buf.getvalue())
+
+
+def save_model(model, quantizer, training_args, path):
+ args, kwargs = model._init_args_kwargs
+ klass = model.__class__
+
+ state = get_state(model, quantizer)
+
+ save_to = path
+ package = {
+ 'klass': klass,
+ 'args': args,
+ 'kwargs': kwargs,
+ 'state': state,
+ 'training_args': training_args,
+ }
+ th.save(package, save_to)
+
+
+def capture_init(init):
+ @functools.wraps(init)
+ def __init__(self, *args, **kwargs):
+ self._init_args_kwargs = (args, kwargs)
+ init(self, *args, **kwargs)
+
+ return __init__
diff --git a/demucs/wav.py b/demucs/wav.py
new file mode 100644
index 0000000000000000000000000000000000000000..a65c3b2ba5aacb1fcab3753f1f85ff7b8db7fc11
--- /dev/null
+++ b/demucs/wav.py
@@ -0,0 +1,174 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+from collections import OrderedDict
+import hashlib
+import math
+import json
+from pathlib import Path
+
+import julius
+import torch as th
+from torch import distributed
+import torchaudio as ta
+from torch.nn import functional as F
+
+from .audio import convert_audio_channels
+from .compressed import get_musdb_tracks
+
+MIXTURE = "mixture"
+EXT = ".wav"
+
+
+def _track_metadata(track, sources):
+ track_length = None
+ track_samplerate = None
+ for source in sources + [MIXTURE]:
+ file = track / f"{source}{EXT}"
+ info = ta.info(str(file))
+ length = info.num_frames
+ if track_length is None:
+ track_length = length
+ track_samplerate = info.sample_rate
+ elif track_length != length:
+ raise ValueError(
+ f"Invalid length for file {file}: "
+ f"expecting {track_length} but got {length}.")
+ elif info.sample_rate != track_samplerate:
+ raise ValueError(
+ f"Invalid sample rate for file {file}: "
+ f"expecting {track_samplerate} but got {info.sample_rate}.")
+ if source == MIXTURE:
+ wav, _ = ta.load(str(file))
+ wav = wav.mean(0)
+ mean = wav.mean().item()
+ std = wav.std().item()
+
+ return {"length": length, "mean": mean, "std": std, "samplerate": track_samplerate}
+
+
+def _build_metadata(path, sources):
+ meta = {}
+ path = Path(path)
+ for file in path.iterdir():
+ meta[file.name] = _track_metadata(file, sources)
+ return meta
+
+
+class Wavset:
+ def __init__(
+ self,
+ root, metadata, sources,
+ length=None, stride=None, normalize=True,
+ samplerate=44100, channels=2):
+ """
+ Waveset (or mp3 set for that matter). Can be used to train
+ with arbitrary sources. Each track should be one folder inside of `path`.
+ The folder should contain files named `{source}.{ext}`.
+ Files will be grouped according to `sources` (each source is a list of
+ filenames).
+
+ Sample rate and channels will be converted on the fly.
+
+ `length` is the sample size to extract (in samples, not duration).
+ `stride` is how many samples to move by between each example.
+ """
+ self.root = Path(root)
+ self.metadata = OrderedDict(metadata)
+ self.length = length
+ self.stride = stride or length
+ self.normalize = normalize
+ self.sources = sources
+ self.channels = channels
+ self.samplerate = samplerate
+ self.num_examples = []
+ for name, meta in self.metadata.items():
+ track_length = int(self.samplerate * meta['length'] / meta['samplerate'])
+ if length is None or track_length < length:
+ examples = 1
+ else:
+ examples = int(math.ceil((track_length - self.length) / self.stride) + 1)
+ self.num_examples.append(examples)
+
+ def __len__(self):
+ return sum(self.num_examples)
+
+ def get_file(self, name, source):
+ return self.root / name / f"{source}{EXT}"
+
+ def __getitem__(self, index):
+ for name, examples in zip(self.metadata, self.num_examples):
+ if index >= examples:
+ index -= examples
+ continue
+ meta = self.metadata[name]
+ num_frames = -1
+ offset = 0
+ if self.length is not None:
+ offset = int(math.ceil(
+ meta['samplerate'] * self.stride * index / self.samplerate))
+ num_frames = int(math.ceil(
+ meta['samplerate'] * self.length / self.samplerate))
+ wavs = []
+ for source in self.sources:
+ file = self.get_file(name, source)
+ wav, _ = ta.load(str(file), frame_offset=offset, num_frames=num_frames)
+ wav = convert_audio_channels(wav, self.channels)
+ wavs.append(wav)
+
+ example = th.stack(wavs)
+ example = julius.resample_frac(example, meta['samplerate'], self.samplerate)
+ if self.normalize:
+ example = (example - meta['mean']) / meta['std']
+ if self.length:
+ example = example[..., :self.length]
+ example = F.pad(example, (0, self.length - example.shape[-1]))
+ return example
+
+
+def get_wav_datasets(args, samples, sources):
+ sig = hashlib.sha1(str(args.wav).encode()).hexdigest()[:8]
+ metadata_file = args.metadata / (sig + ".json")
+ train_path = args.wav / "train"
+ valid_path = args.wav / "valid"
+ if not metadata_file.is_file() and args.rank == 0:
+ train = _build_metadata(train_path, sources)
+ valid = _build_metadata(valid_path, sources)
+ json.dump([train, valid], open(metadata_file, "w"))
+ if args.world_size > 1:
+ distributed.barrier()
+ train, valid = json.load(open(metadata_file))
+ train_set = Wavset(train_path, train, sources,
+ length=samples, stride=args.data_stride,
+ samplerate=args.samplerate, channels=args.audio_channels,
+ normalize=args.norm_wav)
+ valid_set = Wavset(valid_path, valid, [MIXTURE] + sources,
+ samplerate=args.samplerate, channels=args.audio_channels,
+ normalize=args.norm_wav)
+ return train_set, valid_set
+
+
+def get_musdb_wav_datasets(args, samples, sources):
+ metadata_file = args.metadata / "musdb_wav.json"
+ root = args.musdb / "train"
+ if not metadata_file.is_file() and args.rank == 0:
+ metadata = _build_metadata(root, sources)
+ json.dump(metadata, open(metadata_file, "w"))
+ if args.world_size > 1:
+ distributed.barrier()
+ metadata = json.load(open(metadata_file))
+
+ train_tracks = get_musdb_tracks(args.musdb, is_wav=True, subsets=["train"], split="train")
+ metadata_train = {name: meta for name, meta in metadata.items() if name in train_tracks}
+ metadata_valid = {name: meta for name, meta in metadata.items() if name not in train_tracks}
+ train_set = Wavset(root, metadata_train, sources,
+ length=samples, stride=args.data_stride,
+ samplerate=args.samplerate, channels=args.audio_channels,
+ normalize=args.norm_wav)
+ valid_set = Wavset(root, metadata_valid, [MIXTURE] + sources,
+ samplerate=args.samplerate, channels=args.audio_channels,
+ normalize=args.norm_wav)
+ return train_set, valid_set
diff --git a/diffq/__init__.py b/diffq/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b997ee4ed99a90cc43db7812383927e6fe1a3e8
--- /dev/null
+++ b/diffq/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+# flake8: noqa
+"""
+This package implements different quantization strategies:
+
+- `diffq.uniform.UniformQuantizer`: classic uniform quantization over n bits.
+- `diffq.diffq.DiffQuantizer`: differentiable quantizer based on scaled noise injection.
+
+Also, do check `diffq.base.BaseQuantizer` for the common methods of all Quantizers.
+"""
+
+from .uniform import UniformQuantizer
+from .diffq import DiffQuantizer
diff --git a/diffq/base.py b/diffq/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bd5276b51fbed3d4b898a45b93479ff19e62a7b
--- /dev/null
+++ b/diffq/base.py
@@ -0,0 +1,262 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+from dataclasses import dataclass
+from concurrent import futures
+from fnmatch import fnmatch
+from functools import partial
+import io
+import math
+from multiprocessing import cpu_count
+import typing as tp
+import zlib
+
+import torch
+
+
+class BaseQuantizer:
+ @dataclass
+ class _QuantizedParam:
+ name: str
+ param: torch.nn.Parameter
+ module: torch.nn.Module
+ # If a Parameter is used multiple times, `other` can be used
+ # to share state between the different Quantizers
+ other: tp.Optional[tp.Any]
+
+ def __init__(self, model: torch.nn.Module, min_size: float = 0.01, float16: bool = False,
+ exclude: tp.Optional[tp.List[str]] = [], detect_bound: bool = True):
+ self.model = model
+ self.min_size = min_size
+ self.float16 = float16
+ self.exclude = exclude
+ self.detect_bound = detect_bound
+ self._quantized = False
+ self._pre_handle = self.model.register_forward_pre_hook(self._forward_pre_hook)
+ self._post_handle = self.model.register_forward_hook(self._forward_hook)
+
+ self._quantized_state = None
+ self._qparams = []
+ self._float16 = []
+ self._others = []
+ self._rnns = []
+
+ self._saved = []
+
+ self._find_params()
+
+ def _find_params(self):
+ min_params = self.min_size * 2**20 // 4
+ previous = {}
+ for module_name, module in self.model.named_modules():
+ if isinstance(module, torch.nn.RNNBase):
+ self._rnns.append(module)
+ for name, param in list(module.named_parameters(recurse=False)):
+ full_name = f"{module_name}.{name}"
+ matched = False
+ for pattern in self.exclude:
+ if fnmatch(full_name, pattern) or fnmatch(name, pattern):
+ matched = True
+ break
+
+ if param.numel() <= min_params or matched:
+ if id(param) in previous:
+ continue
+ if self.detect_bound:
+ previous[id(param)] = None
+ if self.float16:
+ self._float16.append(param)
+ else:
+ self._others.append(param)
+ else:
+ qparam = self._register_param(name, param, module, previous.get(id(param)))
+ if self.detect_bound:
+ previous[id(param)] = qparam
+ self._qparams.append(qparam)
+
+ def _register_param(self, name, param, module, other):
+ return self.__class__._QuantizedParam(name, param, module, other)
+
+ def _forward_pre_hook(self, module, input):
+ if self.model.training:
+ self._quantized_state = None
+ if self._quantized:
+ self.unquantize()
+ if self._pre_forward_train():
+ self._fix_rnns()
+ else:
+ self.quantize()
+
+ def _forward_hook(self, module, input, output):
+ if self.model.training:
+ if self._post_forward_train():
+ self._fix_rnns(flatten=False) # Hacky, next forward will flatten
+
+ def quantize(self, save=True):
+ """
+ Immediately apply quantization to the model parameters.
+ If `save` is True, save a copy of the unquantized parameters, that can be
+ restored with `unquantize()`.
+ """
+ if self._quantized:
+ return
+ if save:
+ self._saved = [qp.param.data.to('cpu', copy=True)
+ for qp in self._qparams if qp.other is None]
+ self.restore_quantized_state(self.get_quantized_state())
+ self._quantized = True
+ self._fix_rnns()
+
+ def unquantize(self):
+ """
+ Revert a previous call to `quantize()`.
+ """
+ if not self._quantized:
+ raise RuntimeError("Can only be called on a quantized model.")
+ if not self._saved:
+ raise RuntimeError("Nothing to restore.")
+ for qparam in self._qparams:
+ if qparam.other is None:
+ qparam.param.data[:] = self._saved.pop(0)
+ assert len(self._saved) == 0
+ self._quantized = False
+ self._fix_rnns()
+
+ def _pre_forward_train(self) -> bool:
+ """
+ Called once before each forward for continuous quantization.
+ Should return True if parameters were changed.
+ """
+ return False
+
+ def _post_forward_train(self) -> bool:
+ """
+ Called once after each forward (to restore state for instance).
+ Should return True if parameters were changed.
+ """
+ return False
+
+ def _fix_rnns(self, flatten=True):
+ """
+ To be called after quantization happened to fix RNNs.
+ """
+ for rnn in self._rnns:
+ rnn._flat_weights = [
+ (lambda wn: getattr(rnn, wn) if hasattr(rnn, wn) else None)(wn)
+ for wn in rnn._flat_weights_names]
+ if flatten:
+ rnn.flatten_parameters()
+
+ def get_quantized_state(self):
+ """
+ Returns sufficient quantized information to rebuild the model state.
+
+ ..Note::
+ To achieve maximum compression, you should compress this with
+ gzip or other, as quantized weights are not optimally coded!
+ """
+ if self._quantized_state is None:
+ self._quantized_state = self._get_quantized_state()
+ return self._quantized_state
+
+ def _get_quantized_state(self):
+ """
+ Actual implementation for `get_quantized_state`.
+ """
+ float16_params = []
+ for p in self._float16:
+ q = p.data.half()
+ float16_params.append(q)
+
+ return {
+ "quantized": [self._quantize_param(qparam) for qparam in self._qparams
+ if qparam.other is None],
+ "float16": float16_params,
+ "others": [p.data.clone() for p in self._others],
+ }
+
+ def _quantize_param(self, qparam: _QuantizedParam) -> tp.Any:
+ """
+ To be overriden.
+ """
+ raise NotImplementedError()
+
+ def _unquantize_param(self, qparam: _QuantizedParam, quantized: tp.Any) -> torch.Tensor:
+ """
+ To be overriden.
+ """
+ raise NotImplementedError()
+
+ def restore_quantized_state(self, state) -> None:
+ """
+ Restore the state of the model from the quantized state.
+ """
+ for p, q in zip(self._float16, state["float16"]):
+ p.data[:] = q.to(p)
+
+ for p, q in zip(self._others, state["others"]):
+ p.data[:] = q
+
+ remaining = list(state["quantized"])
+ for qparam in self._qparams:
+ if qparam.other is not None:
+ # Only unquantize first appearance of nn.Parameter.
+ continue
+ quantized = remaining.pop(0)
+ qparam.param.data[:] = self._unquantize_param(qparam, quantized)
+ self._fix_rnns()
+
+ def detach(self) -> None:
+ """
+ Detach from the model, removes hooks and anything else.
+ """
+ self._pre_handle.remove()
+ self._post_handle.remove()
+
+ def model_size(self) -> torch.Tensor:
+ """
+ Returns an estimate of the quantized model size.
+ """
+ total = torch.tensor(0.)
+ for p in self._float16:
+ total += 16 * p.numel()
+ for p in self._others:
+ total += 32 * p.numel()
+ return total / 2**20 / 8 # bits to MegaBytes
+
+ def true_model_size(self) -> float:
+ """
+ Return the true quantized model size, in MB, without extra
+ compression.
+ """
+ return self.model_size().item()
+
+ def compressed_model_size(self, compress_level=-1, num_workers=8) -> float:
+ """
+ Return the compressed quantized model size, in MB.
+
+ Args:
+ compress_level (int): compression level used with zlib,
+ see `zlib.compress` for details.
+ num_workers (int): will split the final big byte representation in that
+ many chunks processed in parallels.
+ """
+ out = io.BytesIO()
+ torch.save(self.get_quantized_state(), out)
+ ms = _parallel_compress_len(out.getvalue(), compress_level, num_workers)
+ return ms / 2 ** 20
+
+
+def _compress_len(data, compress_level):
+ return len(zlib.compress(data, level=compress_level))
+
+
+def _parallel_compress_len(data, compress_level, num_workers):
+ num_workers = min(cpu_count(), num_workers)
+ chunk_size = int(math.ceil(len(data) / num_workers))
+ chunks = [data[offset:offset + chunk_size] for offset in range(0, len(data), chunk_size)]
+ with futures.ProcessPoolExecutor(num_workers) as pool:
+ return sum(pool.map(partial(_compress_len, compress_level=compress_level), chunks))
diff --git a/diffq/diffq.py b/diffq/diffq.py
new file mode 100644
index 0000000000000000000000000000000000000000..b475ec7f55227417b014c69b5cf55033182113e1
--- /dev/null
+++ b/diffq/diffq.py
@@ -0,0 +1,286 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+"""
+Differentiable quantizer based on scaled noise injection.
+"""
+from dataclasses import dataclass
+import math
+import typing as tp
+
+import torch
+
+from .base import BaseQuantizer
+from .uniform import uniform_quantize, uniform_unquantize
+from .utils import simple_repr
+
+
+class DiffQuantizer(BaseQuantizer):
+ @dataclass
+ class _QuantizedParam(BaseQuantizer._QuantizedParam):
+ logit: torch.nn.Parameter
+
+ def __init__(self, model: torch.nn.Module, min_size: float = 0.01, float16: bool = False,
+ group_size: int = 1, min_bits: float = 2, max_bits: float = 15,
+ param="bits", noise="gaussian",
+ init_bits: float = 8, extra_bits: float = 0, suffix: str = "_diffq",
+ exclude: tp.List[str] = [], detect_bound: bool = True):
+ """
+ Differentiable quantizer based on scaled noise injection.
+ For every parameter `p` in the model, this introduces a number of bits parameter
+ `b` with the same dimensions (when group_size = 1).
+ Before each forward, `p` is replaced by `p + U`
+ with U uniform iid noise with range [-d/2, d/2], with `d` the uniform quantization
+ step for `b` bits.
+ This noise approximates the quantization noise in a differentiable manner, both
+ with respect to the unquantized parameter `p` and the number of bits `b`.
+
+ At eveluation (as detected with `model.eval()`), the model is replaced
+ by its true quantized version, and restored when going back to training.
+
+ When doing actual quantization (for serialization, or evaluation),
+ the number of bits is rounded to the nearest integer, and needs to be stored along.
+ This will cost a few bits per dimension. To reduce this cost, one can use `group_size`,
+ which will use a single noise level for multiple weight entries.
+
+ You can use the `DiffQuantizer.model_size` method to get a differentiable estimate of the
+ model size in MB. You can then use this estimate as a penalty in your training loss.
+
+ Args:
+ model (torch.nn.Module): model to quantize
+ min_size (float): minimum size in MB of a parameter to be quantized.
+ float16 (bool): if a layer is smaller than min_size, should we still do float16?
+ group_size (int): weight entries are groupped together to reduce the number
+ of noise scales to store. This should divide the size of all parameters
+ bigger than min_size.
+ min_bits (float): minimal number of bits.
+ max_bits (float): maximal number of bits.
+ init_bits (float): initial number of bits.
+ extra_bits (float): extra bits to add for actual quantization (before roundoff).
+ suffix (str): suffix used for the name of the extra noise scale parameters.
+ exclude (list[str]): list of patterns used to match parameters to exclude.
+ For instance `['bias']` to exclude all bias terms.
+ detect_bound (bool): if True, will detect bound parameters and reuse
+ the same quantized tensor for both, as well as the same number of bits.
+
+ ..Warning::
+ You must call `model.training()` and `model.eval()` for `DiffQuantizer` work properly.
+
+ """
+ self.group_size = group_size
+ self.min_bits = min_bits
+ self.max_bits = max_bits
+ self.init_bits = init_bits
+ self.extra_bits = extra_bits
+ self.suffix = suffix
+ self.param = param
+ self.noise = noise
+ assert noise in ["gaussian", "uniform"]
+ self._optimizer_setup = False
+
+ self._min_noise = 1 / (2 ** self.max_bits - 1)
+ self._max_noise = 1 / (2 ** self.min_bits - 1)
+
+ assert group_size >= 0
+ assert min_bits < init_bits < max_bits, \
+ "init_bits must be between min_bits and max_bits excluded3"
+
+ for name, _ in model.named_parameters():
+ if name.endswith(suffix):
+ raise RuntimeError("The model already has some noise scales parameters, "
+ "maybe you used twice a DiffQuantizer on the same model?.")
+
+ super().__init__(model, min_size, float16, exclude, detect_bound)
+
+ def _get_bits(self, logit: torch.Tensor):
+ if self.param == "noise":
+ return torch.log2(1 + 1 / self._get_noise_scale(logit))
+ else:
+ t = torch.sigmoid(logit)
+ return self.max_bits * t + (1 - t) * self.min_bits
+
+ def _get_noise_scale(self, logit: torch.Tensor):
+ if self.param == "noise":
+ t = torch.sigmoid(logit)
+ return torch.exp(t * math.log(self._min_noise) + (1 - t) * math.log(self._max_noise))
+ else:
+ return 1 / (2 ** self._get_bits(logit) - 1)
+
+ def _register_param(self, name, param, module, other):
+ if other is not None:
+ return self.__class__._QuantizedParam(
+ name=name, param=param, module=module, logit=other.logit, other=other)
+ assert self.group_size == 0 or param.numel() % self.group_size == 0
+ # we want the initial number of bits to be init_bits.
+ if self.param == "noise":
+ noise_scale = 1 / (2 ** self.init_bits - 1)
+ t = (math.log(noise_scale) - math.log(self._max_noise)) / (
+ math.log(self._min_noise) - math.log(self._max_noise))
+ else:
+ t = (self.init_bits - self.min_bits) / (self.max_bits - self.min_bits)
+ assert 0 < t < 1
+ logit = torch.logit(torch.tensor(float(t)))
+ assert abs(self._get_bits(logit) - self.init_bits) < 1e-5
+ if self.group_size > 0:
+ nparam = param.numel() // self.group_size
+ else:
+ nparam = 1
+ logit = torch.nn.Parameter(
+ torch.full(
+ (nparam,),
+ logit,
+ device=param.device))
+ module.register_parameter(name + self.suffix, logit)
+ return self.__class__._QuantizedParam(
+ name=name, param=param, module=module, logit=logit, other=None)
+
+ def clear_optimizer(self, optimizer: torch.optim.Optimizer):
+ params = [qp.logit for qp in self._qparams]
+
+ for group in optimizer.param_groups:
+ new_params = []
+ for q in list(group["params"]):
+ matched = False
+ for p in params:
+ if p is q:
+ matched = True
+ if not matched:
+ new_params.append(q)
+ group["params"][:] = new_params
+
+ def setup_optimizer(self, optimizer: torch.optim.Optimizer,
+ lr: float = 1e-3, **kwargs):
+ """
+ Setup the optimizer to tune the number of bits. In particular, this will deactivate
+ weight decay for the bits parameters.
+
+ Args:
+ optimizer (torch.Optimizer): optimizer to use.
+ lr (float): specific learning rate for the bits parameters. 1e-3
+ is perfect for Adam.,w
+ kwargs (dict): overrides for other optimization parameters for the bits.
+ """
+ assert not self._optimizer_setup
+ self._optimizer_setup = True
+
+ params = [qp.logit for qp in self._qparams]
+
+ for group in optimizer.param_groups:
+ for q in list(group["params"]):
+ for p in params:
+ if p is q:
+ raise RuntimeError("You should create the optimizer "
+ "before the quantizer!")
+
+ group = {"params": params, "lr": lr, "weight_decay": 0}
+ group.update(kwargs)
+ optimizer.add_param_group(group)
+
+ def no_optimizer(self):
+ """
+ Call this if you do not want to use an optimizer.
+ """
+ self._optimizer_setup = True
+
+ def check_unused(self):
+ for qparam in self._qparams:
+ if qparam.other is not None:
+ continue
+ grad = qparam.param.grad
+ if grad is None or (grad == 0).all():
+ if qparam.logit.grad is not None:
+ qparam.logit.grad.data.zero_()
+
+ def model_size(self, exact=False):
+ """
+ Differentiable estimate of the model size.
+ The size is returned in MB.
+
+ If `exact` is True, then the output is no longer differentiable but
+ reflect exactly an achievable size, even without compression,
+ i.e.same as returned by `naive_model_size()`.
+ """
+ total = super().model_size()
+ subtotal = 0
+ for qparam in self._qparams:
+ # only count the first appearance of a Parameter
+ if qparam.other is not None:
+ continue
+ bits = self.extra_bits + self._get_bits(qparam.logit)
+ if exact:
+ bits = bits.round().clamp(1, 15)
+ if self.group_size == 0:
+ group_size = qparam.param.numel()
+ else:
+ group_size = self.group_size
+ subtotal += group_size * bits.sum()
+ subtotal += 2 * 32 # param scale
+
+ # Number of bits to represent each number of bits
+ bits_bits = math.ceil(math.log2(1 + (bits.max().round().item() - self.min_bits)))
+ subtotal += 8 # 8 bits for bits_bits
+ subtotal += bits_bits * bits.numel()
+
+ subtotal /= 2 ** 20 * 8 # bits -> MegaBytes
+ return total + subtotal
+
+ def true_model_size(self):
+ """
+ Naive model size without zlib compression.
+ """
+ return self.model_size(exact=True).item()
+
+ def _pre_forward_train(self):
+ if not self._optimizer_setup:
+ raise RuntimeError("You must call `setup_optimizer()` on your optimizer "
+ "before starting training.")
+ for qparam in self._qparams:
+ if qparam.other is not None:
+ noisy = qparam.other.module._parameters[qparam.other.name]
+ else:
+ bits = self._get_bits(qparam.logit)[:, None]
+ if self.group_size == 0:
+ p_flat = qparam.param.view(-1)
+ else:
+ p_flat = qparam.param.view(-1, self.group_size)
+ scale = p_flat.max() - p_flat.min()
+ unit = 1 / (2**bits - 1)
+ if self.noise == "uniform":
+ noise_source = (torch.rand_like(p_flat) - 0.5)
+ elif self.noise == "gaussian":
+ noise_source = torch.randn_like(p_flat) / 2
+ noise = scale * unit * noise_source
+ noisy = p_flat + noise
+ # We bypass the checks by PyTorch on parameters being leafs
+ qparam.module._parameters[qparam.name] = noisy.view_as(qparam.param)
+ return True
+
+ def _post_forward_train(self):
+ for qparam in self._qparams:
+ qparam.module._parameters[qparam.name] = qparam.param
+ return True
+
+ def _quantize_param(self, qparam: _QuantizedParam) -> tp.Any:
+ bits = self.extra_bits + self._get_bits(qparam.logit)
+ bits = bits.round().clamp(1, 15)[:, None].byte()
+ if self.group_size == 0:
+ p = qparam.param.data.view(-1)
+ else:
+ p = qparam.param.data.view(-1, self.group_size)
+ levels, scales = uniform_quantize(p, bits)
+ return levels, scales, bits
+
+ def _unquantize_param(self, qparam: _QuantizedParam, quantized: tp.Any) -> torch.Tensor:
+ levels, param_scale, bits = quantized
+ return uniform_unquantize(levels, param_scale, bits).view_as(qparam.param.data)
+
+ def detach(self):
+ super().detach()
+ for qparam in self._qparams:
+ delattr(qparam.module, qparam.name + self.suffix)
+
+ def __repr__(self):
+ return simple_repr(self)
diff --git a/diffq/uniform.py b/diffq/uniform.py
new file mode 100644
index 0000000000000000000000000000000000000000..f61e9129c04caaa33c66f726bf2433d51689cfa5
--- /dev/null
+++ b/diffq/uniform.py
@@ -0,0 +1,121 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+"""
+Classic uniform quantization over n bits.
+"""
+from typing import Tuple
+import torch
+
+from .base import BaseQuantizer
+from .utils import simple_repr
+
+
+def uniform_quantize(p: torch.Tensor, bits: torch.Tensor = torch.tensor(8.)):
+ """
+ Quantize the given weights over `bits` bits.
+
+ Returns:
+ - quantized levels
+ - (min, max) range.
+
+ """
+ assert (bits >= 1).all() and (bits <= 15).all()
+ num_levels = (2 ** bits.float()).long()
+ mn = p.min().item()
+ mx = p.max().item()
+ p = (p - mn) / (mx - mn) # put p in [0, 1]
+ unit = 1 / (num_levels - 1) # quantization unit
+ levels = (p / unit).round()
+ if (bits <= 8).all():
+ levels = levels.byte()
+ else:
+ levels = levels.short()
+ return levels, (mn, mx)
+
+
+def uniform_unquantize(levels: torch.Tensor, scales: Tuple[float, float],
+ bits: torch.Tensor = torch.tensor(8.)):
+ """
+ Unquantize the weights from the levels and scale. Return a float32 tensor.
+ """
+ mn, mx = scales
+ num_levels = 2 ** bits.float()
+ unit = 1 / (num_levels - 1)
+ levels = levels.float()
+ p = levels * unit # in [0, 1]
+ return p * (mx - mn) + mn
+
+
+class UniformQuantizer(BaseQuantizer):
+ def __init__(self, model: torch.nn.Module, bits: float = 8., min_size: float = 0.01,
+ float16: bool = False, qat: bool = False, exclude=[], detect_bound=True):
+ """
+ Args:
+ model (torch.nn.Module): model to quantize
+ bits (float): number of bits to quantize over.
+ min_size (float): minimum size in MB of a parameter to be quantized.
+ float16 (bool): if a layer is smaller than min_size, should we still do float16?
+ qat (bool): perform quantized aware training.
+ exclude (list[str]): list of patterns used to match parameters to exclude.
+ For instance `['bias']` to exclude all bias terms.
+ detect_bound (bool): if True, will detect bound parameters and reuse
+ the same quantized tensor for both.
+ """
+ self.bits = float(bits)
+ self.qat = qat
+
+ super().__init__(model, min_size, float16, exclude, detect_bound)
+
+ def __repr__(self):
+ return simple_repr(self, )
+
+ def _pre_forward_train(self):
+ if self.qat:
+ for qparam in self._qparams:
+ if qparam.other is not None:
+ new_param = qparam.other.module._parameters[qparam.other.name]
+ else:
+ quantized = self._quantize_param(qparam)
+ qvalue = self._unquantize_param(qparam, quantized)
+ new_param = qparam.param + (qvalue - qparam.param).detach()
+ qparam.module._parameters[qparam.name] = new_param
+ return True
+ return False
+
+ def _post_forward_train(self):
+ if self.qat:
+ for qparam in self._qparams:
+ qparam.module._parameters[qparam.name] = qparam.param
+ return True
+ return False
+
+ def _quantize_param(self, qparam):
+ levels, scales = uniform_quantize(qparam.param.data, torch.tensor(self.bits))
+ return (levels, scales)
+
+ def _unquantize_param(self, qparam, quantized):
+ levels, scales = quantized
+ return uniform_unquantize(levels, scales, torch.tensor(self.bits))
+
+ def model_size(self):
+ """
+ Non differentiable model size in MB.
+ """
+ total = super().model_size()
+ subtotal = 0
+ for qparam in self._qparams:
+ if qparam.other is None: # if parameter is bound, count only one copy.
+ subtotal += self.bits * qparam.param.numel() + 64 # 2 float for the overall scales
+ subtotal /= 2**20 * 8 # bits to MegaBytes
+ return total + subtotal
+
+ def true_model_size(self):
+ """
+ Return the true quantized model size, in MB, without extra
+ compression.
+ """
+ return self.model_size().item()
diff --git a/diffq/utils.py b/diffq/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..be6ab5253c38564140bc202077292bb99f9f397b
--- /dev/null
+++ b/diffq/utils.py
@@ -0,0 +1,37 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import inspect
+from typing import Optional, List
+
+
+def simple_repr(obj, attrs: Optional[List[str]] = None, overrides={}):
+ """
+ Return a simple representation string for `obj`.
+ If `attrs` is not None, it should be a list of attributes to include.
+ """
+ params = inspect.signature(obj.__class__).parameters
+ attrs_repr = []
+ if attrs is None:
+ attrs = params.keys()
+ for attr in attrs:
+ display = False
+ if attr in overrides:
+ value = overrides[attr]
+ elif hasattr(obj, attr):
+ value = getattr(obj, attr)
+ else:
+ continue
+ if attr in params:
+ param = params[attr]
+ if param.default is inspect._empty or value != param.default:
+ display = True
+ else:
+ display = True
+
+ if display:
+ attrs_repr.append(f"{attr}={value}")
+ return f"{obj.__class__.__name__}({','.join(attrs_repr)})"
diff --git a/easy_infer.py b/easy_infer.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f32bc37e6702cd7288df66059d4a09bc0e3d6aa
--- /dev/null
+++ b/easy_infer.py
@@ -0,0 +1,1398 @@
+import subprocess
+import os
+import sys
+import errno
+import shutil
+import yt_dlp
+from mega import Mega
+import datetime
+import unicodedata
+import torch
+import glob
+import gradio as gr
+import gdown
+import zipfile
+import traceback
+import json
+import mdx
+from mdx_processing_script import get_model_list,id_to_ptm,prepare_mdx,run_mdx
+import requests
+import wget
+import ffmpeg
+import hashlib
+now_dir = os.getcwd()
+sys.path.append(now_dir)
+from unidecode import unidecode
+import re
+import time
+from lib.infer_pack.models_onnx import SynthesizerTrnMsNSFsidM
+from infer.modules.vc.pipeline import Pipeline
+VC = Pipeline
+from lib.infer_pack.models import (
+ SynthesizerTrnMs256NSFsid,
+ SynthesizerTrnMs256NSFsid_nono,
+ SynthesizerTrnMs768NSFsid,
+ SynthesizerTrnMs768NSFsid_nono,
+)
+from MDXNet import MDXNetDereverb
+from configs.config import Config
+from infer_uvr5 import _audio_pre_, _audio_pre_new
+from huggingface_hub import HfApi, list_models
+from huggingface_hub import login
+from i18n import I18nAuto
+i18n = I18nAuto()
+from bs4 import BeautifulSoup
+from sklearn.cluster import MiniBatchKMeans
+from dotenv import load_dotenv
+load_dotenv()
+config = Config()
+tmp = os.path.join(now_dir, "TEMP")
+shutil.rmtree(tmp, ignore_errors=True)
+os.environ["TEMP"] = tmp
+weight_root = os.getenv("weight_root")
+weight_uvr5_root = os.getenv("weight_uvr5_root")
+index_root = os.getenv("index_root")
+audio_root = "audios"
+names = []
+for name in os.listdir(weight_root):
+ if name.endswith(".pth"):
+ names.append(name)
+index_paths = []
+
+global indexes_list
+indexes_list = []
+
+audio_paths = []
+for root, dirs, files in os.walk(index_root, topdown=False):
+ for name in files:
+ if name.endswith(".index") and "trained" not in name:
+ index_paths.append("%s\\%s" % (root, name))
+
+for root, dirs, files in os.walk(audio_root, topdown=False):
+ for name in files:
+ audio_paths.append("%s/%s" % (root, name))
+
+uvr5_names = []
+for name in os.listdir(weight_uvr5_root):
+ if name.endswith(".pth") or "onnx" in name:
+ uvr5_names.append(name.replace(".pth", ""))
+
+def calculate_md5(file_path):
+ hash_md5 = hashlib.md5()
+ with open(file_path, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ hash_md5.update(chunk)
+ return hash_md5.hexdigest()
+
+def format_title(title):
+ formatted_title = re.sub(r'[^\w\s-]', '', title)
+ formatted_title = formatted_title.replace(" ", "_")
+ return formatted_title
+
+def silentremove(filename):
+ try:
+ os.remove(filename)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+def get_md5(temp_folder):
+ for root, subfolders, files in os.walk(temp_folder):
+ for file in files:
+ if not file.startswith("G_") and not file.startswith("D_") and file.endswith(".pth") and not "_G_" in file and not "_D_" in file:
+ md5_hash = calculate_md5(os.path.join(root, file))
+ return md5_hash
+
+ return None
+
+def find_parent(search_dir, file_name):
+ for dirpath, dirnames, filenames in os.walk(search_dir):
+ if file_name in filenames:
+ return os.path.abspath(dirpath)
+ return None
+
+def find_folder_parent(search_dir, folder_name):
+ for dirpath, dirnames, filenames in os.walk(search_dir):
+ if folder_name in dirnames:
+ return os.path.abspath(dirpath)
+ return None
+
+
+def delete_large_files(directory_path, max_size_megabytes):
+ for filename in os.listdir(directory_path):
+ file_path = os.path.join(directory_path, filename)
+ if os.path.isfile(file_path):
+ size_in_bytes = os.path.getsize(file_path)
+ size_in_megabytes = size_in_bytes / (1024 * 1024) # Convert bytes to megabytes
+
+ if size_in_megabytes > max_size_megabytes:
+ print("###################################")
+ print(f"Deleting s*** {filename} (Size: {size_in_megabytes:.2f} MB)")
+ os.remove(file_path)
+ print("###################################")
+
+def download_from_url(url):
+ parent_path = find_folder_parent(".", "pretrained_v2")
+ zips_path = os.path.join(parent_path, 'zips')
+ print(f"Limit download size in MB {os.getenv('MAX_DOWNLOAD_SIZE')}, duplicate the space for modify the limit")
+
+ if url != '':
+ print(i18n("Downloading the file: ") + f"{url}")
+ if "drive.google.com" in url:
+ if "file/d/" in url:
+ file_id = url.split("file/d/")[1].split("/")[0]
+ elif "id=" in url:
+ file_id = url.split("id=")[1].split("&")[0]
+ else:
+ return None
+
+ if file_id:
+ os.chdir('./zips')
+ result = subprocess.run(["gdown", f"https://drive.google.com/uc?id={file_id}", "--fuzzy"], capture_output=True, text=True, encoding='utf-8')
+ if "Too many users have viewed or downloaded this file recently" in str(result.stderr):
+ return "too much use"
+ if "Cannot retrieve the public link of the file." in str(result.stderr):
+ return "private link"
+ print(result.stderr)
+
+ elif "/blob/" in url:
+ os.chdir('./zips')
+ url = url.replace("blob", "resolve")
+ response = requests.get(url)
+ if response.status_code == 200:
+ file_name = url.split('/')[-1]
+ with open(os.path.join(zips_path, file_name), "wb") as newfile:
+ newfile.write(response.content)
+ else:
+ os.chdir(parent_path)
+ elif "mega.nz" in url:
+ if "#!" in url:
+ file_id = url.split("#!")[1].split("!")[0]
+ elif "file/" in url:
+ file_id = url.split("file/")[1].split("/")[0]
+ else:
+ return None
+ if file_id:
+ m = Mega()
+ m.download_url(url, zips_path)
+ elif "/tree/main" in url:
+ response = requests.get(url)
+ soup = BeautifulSoup(response.content, 'html.parser')
+ temp_url = ''
+ for link in soup.find_all('a', href=True):
+ if link['href'].endswith('.zip'):
+ temp_url = link['href']
+ break
+ if temp_url:
+ url = temp_url
+ url = url.replace("blob", "resolve")
+ if "huggingface.co" not in url:
+ url = "https://huggingface.co" + url
+
+ wget.download(url)
+ else:
+ print("No .zip file found on the page.")
+ elif "cdn.discordapp.com" in url:
+ file = requests.get(url)
+ if file.status_code == 200:
+ name = url.split('/')
+ with open(os.path.join(zips_path, name[len(name)-1]), "wb") as newfile:
+ newfile.write(file.content)
+ else:
+ return None
+ elif "pixeldrain.com" in url:
+ try:
+ file_id = url.split("pixeldrain.com/u/")[1]
+ os.chdir('./zips')
+ print(file_id)
+ response = requests.get(f"https://pixeldrain.com/api/file/{file_id}")
+ if response.status_code == 200:
+ file_name = response.headers.get("Content-Disposition").split('filename=')[-1].strip('";')
+ if not os.path.exists(zips_path):
+ os.makedirs(zips_path)
+ with open(os.path.join(zips_path, file_name), "wb") as newfile:
+ newfile.write(response.content)
+ os.chdir(parent_path)
+ return "downloaded"
+ else:
+ os.chdir(parent_path)
+ return None
+ except Exception as e:
+ print(e)
+ os.chdir(parent_path)
+ return None
+ else:
+ os.chdir('./zips')
+ wget.download(url)
+
+ #os.chdir('./zips')
+ delete_large_files(zips_path, int(os.getenv("MAX_DOWNLOAD_SIZE")))
+ os.chdir(parent_path)
+ print(i18n("Full download"))
+ return "downloaded"
+ else:
+ return None
+
+class error_message(Exception):
+ def __init__(self, mensaje):
+ self.mensaje = mensaje
+ super().__init__(mensaje)
+
+def get_vc(sid, to_return_protect0, to_return_protect1):
+ global n_spk, tgt_sr, net_g, vc, cpt, version
+ if sid == "" or sid == []:
+ global hubert_model
+ if hubert_model is not None:
+ print("clean_empty_cache")
+ del net_g, n_spk, vc, hubert_model, tgt_sr
+ hubert_model = net_g = n_spk = vc = hubert_model = tgt_sr = None
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+ if_f0 = cpt.get("f0", 1)
+ version = cpt.get("version", "v1")
+ if version == "v1":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs256NSFsid(
+ *cpt["config"], is_half=config.is_half
+ )
+ else:
+ net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"])
+ elif version == "v2":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs768NSFsid(
+ *cpt["config"], is_half=config.is_half
+ )
+ else:
+ net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"])
+ del net_g, cpt
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+ cpt = None
+ return (
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ )
+ person = "%s/%s" % (weight_root, sid)
+ print("loading %s" % person)
+ cpt = torch.load(person, map_location="cpu")
+ tgt_sr = cpt["config"][-1]
+ cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0]
+ if_f0 = cpt.get("f0", 1)
+ if if_f0 == 0:
+ to_return_protect0 = to_return_protect1 = {
+ "visible": False,
+ "value": 0.5,
+ "__type__": "update",
+ }
+ else:
+ to_return_protect0 = {
+ "visible": True,
+ "value": to_return_protect0,
+ "__type__": "update",
+ }
+ to_return_protect1 = {
+ "visible": True,
+ "value": to_return_protect1,
+ "__type__": "update",
+ }
+ version = cpt.get("version", "v1")
+ if version == "v1":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs256NSFsid(*cpt["config"], is_half=config.is_half)
+ else:
+ net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"])
+ elif version == "v2":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs768NSFsid(*cpt["config"], is_half=config.is_half)
+ else:
+ net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"])
+ del net_g.enc_q
+ print(net_g.load_state_dict(cpt["weight"], strict=False))
+ net_g.eval().to(config.device)
+ if config.is_half:
+ net_g = net_g.half()
+ else:
+ net_g = net_g.float()
+ vc = VC(tgt_sr, config)
+ n_spk = cpt["config"][-3]
+ return (
+ {"visible": True, "maximum": n_spk, "__type__": "update"},
+ to_return_protect0,
+ to_return_protect1,
+ )
+
+def load_downloaded_model(url):
+ parent_path = find_folder_parent(".", "pretrained_v2")
+ try:
+ infos = []
+ logs_folders = ['0_gt_wavs','1_16k_wavs','2a_f0','2b-f0nsf','3_feature256','3_feature768']
+ zips_path = os.path.join(parent_path, 'zips')
+ unzips_path = os.path.join(parent_path, 'unzips')
+ weights_path = os.path.join(parent_path, 'weights')
+ logs_dir = ""
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+ if os.path.exists(unzips_path):
+ shutil.rmtree(unzips_path)
+
+ os.mkdir(zips_path)
+ os.mkdir(unzips_path)
+
+ download_file = download_from_url(url)
+ if not download_file:
+ print(i18n("The file could not be downloaded."))
+ infos.append(i18n("The file could not be downloaded."))
+ yield "\n".join(infos)
+ elif download_file == "downloaded":
+ print(i18n("It has been downloaded successfully."))
+ infos.append(i18n("It has been downloaded successfully."))
+ yield "\n".join(infos)
+ elif download_file == "too much use":
+ raise Exception(i18n("Too many users have recently viewed or downloaded this file"))
+ elif download_file == "private link":
+ raise Exception(i18n("Cannot get file from this private link"))
+
+ for filename in os.listdir(zips_path):
+ if filename.endswith(".zip"):
+ zipfile_path = os.path.join(zips_path,filename)
+ print(i18n("Proceeding with the extraction..."))
+ infos.append(i18n("Proceeding with the extraction..."))
+ shutil.unpack_archive(zipfile_path, unzips_path, 'zip')
+ model_name = os.path.basename(zipfile_path)
+ logs_dir = os.path.join(parent_path,'logs', os.path.normpath(str(model_name).replace(".zip","")))
+ yield "\n".join(infos)
+ else:
+ print(i18n("Unzip error."))
+ infos.append(i18n("Unzip error."))
+ yield "\n".join(infos)
+
+ index_file = False
+ model_file = False
+ D_file = False
+ G_file = False
+
+ for path, subdirs, files in os.walk(unzips_path):
+ for item in files:
+ item_path = os.path.join(path, item)
+ if not 'G_' in item and not 'D_' in item and item.endswith('.pth'):
+ model_file = True
+ model_name = item.replace(".pth","")
+ logs_dir = os.path.join(parent_path,'logs', model_name)
+ if os.path.exists(logs_dir):
+ shutil.rmtree(logs_dir)
+ os.mkdir(logs_dir)
+ if not os.path.exists(weights_path):
+ os.mkdir(weights_path)
+ if os.path.exists(os.path.join(weights_path, item)):
+ os.remove(os.path.join(weights_path, item))
+ if os.path.exists(item_path):
+ shutil.move(item_path, weights_path)
+
+ if not model_file and not os.path.exists(logs_dir):
+ os.mkdir(logs_dir)
+ for path, subdirs, files in os.walk(unzips_path):
+ for item in files:
+ item_path = os.path.join(path, item)
+ if item.startswith('added_') and item.endswith('.index'):
+ index_file = True
+ if os.path.exists(item_path):
+ if os.path.exists(os.path.join(logs_dir, item)):
+ os.remove(os.path.join(logs_dir, item))
+ shutil.move(item_path, logs_dir)
+ if item.startswith('total_fea.npy') or item.startswith('events.'):
+ if os.path.exists(item_path):
+ if os.path.exists(os.path.join(logs_dir, item)):
+ os.remove(os.path.join(logs_dir, item))
+ shutil.move(item_path, logs_dir)
+
+
+ result = ""
+ if model_file:
+ if index_file:
+ print(i18n("The model works for inference, and has the .index file."))
+ infos.append("\n" + i18n("The model works for inference, and has the .index file."))
+ yield "\n".join(infos)
+ else:
+ print(i18n("The model works for inference, but it doesn't have the .index file."))
+ infos.append("\n" + i18n("The model works for inference, but it doesn't have the .index file."))
+ yield "\n".join(infos)
+
+ if not index_file and not model_file:
+ print(i18n("No relevant file was found to upload."))
+ infos.append(i18n("No relevant file was found to upload."))
+ yield "\n".join(infos)
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+ if os.path.exists(unzips_path):
+ shutil.rmtree(unzips_path)
+ os.chdir(parent_path)
+ return result
+ except Exception as e:
+ os.chdir(parent_path)
+ if "too much use" in str(e):
+ print(i18n("Too many users have recently viewed or downloaded this file"))
+ yield i18n("Too many users have recently viewed or downloaded this file")
+ elif "private link" in str(e):
+ print(i18n("Cannot get file from this private link"))
+ yield i18n("Cannot get file from this private link")
+ else:
+ print(e)
+ yield i18n("An error occurred downloading")
+ finally:
+ os.chdir(parent_path)
+
+def load_dowloaded_dataset(url):
+ parent_path = find_folder_parent(".", "pretrained_v2")
+ infos = []
+ try:
+ zips_path = os.path.join(parent_path, 'zips')
+ unzips_path = os.path.join(parent_path, 'unzips')
+ datasets_path = os.path.join(parent_path, 'datasets')
+ audio_extenions =['wav', 'mp3', 'flac', 'ogg', 'opus',
+ 'm4a', 'mp4', 'aac', 'alac', 'wma',
+ 'aiff', 'webm', 'ac3']
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+ if os.path.exists(unzips_path):
+ shutil.rmtree(unzips_path)
+
+ if not os.path.exists(datasets_path):
+ os.mkdir(datasets_path)
+
+ os.mkdir(zips_path)
+ os.mkdir(unzips_path)
+
+ download_file = download_from_url(url)
+
+ if not download_file:
+ print(i18n("An error occurred downloading"))
+ infos.append(i18n("An error occurred downloading"))
+ yield "\n".join(infos)
+ raise Exception(i18n("An error occurred downloading"))
+ elif download_file == "downloaded":
+ print(i18n("It has been downloaded successfully."))
+ infos.append(i18n("It has been downloaded successfully."))
+ yield "\n".join(infos)
+ elif download_file == "too much use":
+ raise Exception(i18n("Too many users have recently viewed or downloaded this file"))
+ elif download_file == "private link":
+ raise Exception(i18n("Cannot get file from this private link"))
+
+ zip_path = os.listdir(zips_path)
+ foldername = ""
+ for file in zip_path:
+ if file.endswith('.zip'):
+ file_path = os.path.join(zips_path, file)
+ print("....")
+ foldername = file.replace(".zip","").replace(" ","").replace("-","_")
+ dataset_path = os.path.join(datasets_path, foldername)
+ print(i18n("Proceeding with the extraction..."))
+ infos.append(i18n("Proceeding with the extraction..."))
+ yield "\n".join(infos)
+ shutil.unpack_archive(file_path, unzips_path, 'zip')
+ if os.path.exists(dataset_path):
+ shutil.rmtree(dataset_path)
+
+ os.mkdir(dataset_path)
+
+ for root, subfolders, songs in os.walk(unzips_path):
+ for song in songs:
+ song_path = os.path.join(root, song)
+ if song.endswith(tuple(audio_extenions)):
+ formatted_song_name = format_title(os.path.splitext(song)[0])
+ extension = os.path.splitext(song)[1]
+ new_song_path = os.path.join(dataset_path, f"{formatted_song_name}{extension}")
+ shutil.move(song_path, new_song_path)
+ else:
+ print(i18n("Unzip error."))
+ infos.append(i18n("Unzip error."))
+ yield "\n".join(infos)
+
+
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+ if os.path.exists(unzips_path):
+ shutil.rmtree(unzips_path)
+
+ print(i18n("The Dataset has been loaded successfully."))
+ infos.append(i18n("The Dataset has been loaded successfully."))
+ yield "\n".join(infos)
+ except Exception as e:
+ os.chdir(parent_path)
+ if "too much use" in str(e):
+ print(i18n("Too many users have recently viewed or downloaded this file"))
+ yield i18n("Too many users have recently viewed or downloaded this file")
+ elif "private link" in str(e):
+ print(i18n("Cannot get file from this private link"))
+ yield i18n("Cannot get file from this private link")
+ else:
+ print(e)
+ yield i18n("An error occurred downloading")
+ finally:
+ os.chdir(parent_path)
+
+def save_model(modelname, save_action):
+
+ parent_path = find_folder_parent(".", "pretrained_v2")
+ zips_path = os.path.join(parent_path, 'zips')
+ dst = os.path.join(zips_path,modelname)
+ logs_path = os.path.join(parent_path, 'logs', modelname)
+ weights_path = os.path.join(parent_path, 'weights', f"{modelname}.pth")
+ save_folder = parent_path
+ infos = []
+
+ try:
+ if not os.path.exists(logs_path):
+ raise Exception("No model found.")
+
+ if not 'content' in parent_path:
+ save_folder = os.path.join(parent_path, 'RVC_Backup')
+ else:
+ save_folder = '/content/drive/MyDrive/RVC_Backup'
+
+ infos.append(i18n("Save model"))
+ yield "\n".join(infos)
+
+ if not os.path.exists(save_folder):
+ os.mkdir(save_folder)
+ if not os.path.exists(os.path.join(save_folder, 'ManualTrainingBackup')):
+ os.mkdir(os.path.join(save_folder, 'ManualTrainingBackup'))
+ if not os.path.exists(os.path.join(save_folder, 'Finished')):
+ os.mkdir(os.path.join(save_folder, 'Finished'))
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+
+ os.mkdir(zips_path)
+ added_file = glob.glob(os.path.join(logs_path, "added_*.index"))
+ d_file = glob.glob(os.path.join(logs_path, "D_*.pth"))
+ g_file = glob.glob(os.path.join(logs_path, "G_*.pth"))
+
+ if save_action == i18n("Choose the method"):
+ raise Exception("No method choosen.")
+
+ if save_action == i18n("Save all"):
+ print(i18n("Save all"))
+ save_folder = os.path.join(save_folder, 'ManualTrainingBackup')
+ shutil.copytree(logs_path, dst)
+ else:
+ if not os.path.exists(dst):
+ os.mkdir(dst)
+
+ if save_action == i18n("Save D and G"):
+ print(i18n("Save D and G"))
+ save_folder = os.path.join(save_folder, 'ManualTrainingBackup')
+ if len(d_file) > 0:
+ shutil.copy(d_file[0], dst)
+ if len(g_file) > 0:
+ shutil.copy(g_file[0], dst)
+
+ if len(added_file) > 0:
+ shutil.copy(added_file[0], dst)
+ else:
+ infos.append(i18n("Saved without index..."))
+
+ if save_action == i18n("Save voice"):
+ print(i18n("Save voice"))
+ save_folder = os.path.join(save_folder, 'Finished')
+ if len(added_file) > 0:
+ shutil.copy(added_file[0], dst)
+ else:
+ infos.append(i18n("Saved without index..."))
+
+ yield "\n".join(infos)
+ if not os.path.exists(weights_path):
+ infos.append(i18n("Saved without inference model..."))
+ else:
+ shutil.copy(weights_path, dst)
+
+ yield "\n".join(infos)
+ infos.append("\n" + i18n("This may take a few minutes, please wait..."))
+ yield "\n".join(infos)
+
+ shutil.make_archive(os.path.join(zips_path,f"{modelname}"), 'zip', zips_path)
+ shutil.move(os.path.join(zips_path,f"{modelname}.zip"), os.path.join(save_folder, f'{modelname}.zip'))
+
+ shutil.rmtree(zips_path)
+ infos.append("\n" + i18n("Model saved successfully"))
+ yield "\n".join(infos)
+
+ except Exception as e:
+ print(e)
+ if "No model found." in str(e):
+ infos.append(i18n("The model you want to save does not exist, be sure to enter the correct name."))
+ else:
+ infos.append(i18n("An error occurred saving the model"))
+
+ yield "\n".join(infos)
+
+def load_downloaded_backup(url):
+ parent_path = find_folder_parent(".", "pretrained_v2")
+ try:
+ infos = []
+ logs_folders = ['0_gt_wavs','1_16k_wavs','2a_f0','2b-f0nsf','3_feature256','3_feature768']
+ zips_path = os.path.join(parent_path, 'zips')
+ unzips_path = os.path.join(parent_path, 'unzips')
+ weights_path = os.path.join(parent_path, 'weights')
+ logs_dir = os.path.join(parent_path, 'logs')
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+ if os.path.exists(unzips_path):
+ shutil.rmtree(unzips_path)
+
+ os.mkdir(zips_path)
+ os.mkdir(unzips_path)
+
+ download_file = download_from_url(url)
+ if not download_file:
+ print(i18n("The file could not be downloaded."))
+ infos.append(i18n("The file could not be downloaded."))
+ yield "\n".join(infos)
+ elif download_file == "downloaded":
+ print(i18n("It has been downloaded successfully."))
+ infos.append(i18n("It has been downloaded successfully."))
+ yield "\n".join(infos)
+ elif download_file == "too much use":
+ raise Exception(i18n("Too many users have recently viewed or downloaded this file"))
+ elif download_file == "private link":
+ raise Exception(i18n("Cannot get file from this private link"))
+
+ for filename in os.listdir(zips_path):
+ if filename.endswith(".zip"):
+ zipfile_path = os.path.join(zips_path,filename)
+ zip_dir_name = os.path.splitext(filename)[0]
+ unzip_dir = unzips_path
+ print(i18n("Proceeding with the extraction..."))
+ infos.append(i18n("Proceeding with the extraction..."))
+ shutil.unpack_archive(zipfile_path, unzip_dir, 'zip')
+
+ if os.path.exists(os.path.join(unzip_dir, zip_dir_name)):
+ shutil.move(os.path.join(unzip_dir, zip_dir_name), logs_dir)
+ else:
+ new_folder_path = os.path.join(logs_dir, zip_dir_name)
+ os.mkdir(new_folder_path)
+ for item_name in os.listdir(unzip_dir):
+ item_path = os.path.join(unzip_dir, item_name)
+ if os.path.isfile(item_path):
+ shutil.move(item_path, new_folder_path)
+ elif os.path.isdir(item_path):
+ shutil.move(item_path, new_folder_path)
+
+ yield "\n".join(infos)
+ else:
+ print(i18n("Unzip error."))
+ infos.append(i18n("Unzip error."))
+ yield "\n".join(infos)
+
+ result = ""
+
+ for filename in os.listdir(unzips_path):
+ if filename.endswith(".zip"):
+ silentremove(filename)
+
+ if os.path.exists(zips_path):
+ shutil.rmtree(zips_path)
+ if os.path.exists(os.path.join(parent_path, 'unzips')):
+ shutil.rmtree(os.path.join(parent_path, 'unzips'))
+ print(i18n("The Backup has been uploaded successfully."))
+ infos.append("\n" + i18n("The Backup has been uploaded successfully."))
+ yield "\n".join(infos)
+ os.chdir(parent_path)
+ return result
+ except Exception as e:
+ os.chdir(parent_path)
+ if "too much use" in str(e):
+ print(i18n("Too many users have recently viewed or downloaded this file"))
+ yield i18n("Too many users have recently viewed or downloaded this file")
+ elif "private link" in str(e):
+ print(i18n("Cannot get file from this private link"))
+ yield i18n("Cannot get file from this private link")
+ else:
+ print(e)
+ yield i18n("An error occurred downloading")
+ finally:
+ os.chdir(parent_path)
+
+def save_to_wav(record_button):
+ if record_button is None:
+ pass
+ else:
+ path_to_file=record_button
+ new_name = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")+'.wav'
+ new_path='./audios/'+new_name
+ shutil.move(path_to_file,new_path)
+ return new_name
+
+
+def change_choices2():
+ audio_paths=[]
+ for filename in os.listdir("./audios"):
+ if filename.endswith(('wav', 'mp3', 'flac', 'ogg', 'opus',
+ 'm4a', 'mp4', 'aac', 'alac', 'wma',
+ 'aiff', 'webm', 'ac3')):
+ audio_paths.append(os.path.join('./audios',filename).replace('\\', '/'))
+ return {"choices": sorted(audio_paths), "__type__": "update"}, {"__type__": "update"}
+
+
+
+
+
+def uvr(input_url, output_path, model_name, inp_root, save_root_vocal, paths, save_root_ins, agg, format0, architecture):
+ carpeta_a_eliminar = "yt_downloads"
+ if os.path.exists(carpeta_a_eliminar) and os.path.isdir(carpeta_a_eliminar):
+ for archivo in os.listdir(carpeta_a_eliminar):
+ ruta_archivo = os.path.join(carpeta_a_eliminar, archivo)
+ if os.path.isfile(ruta_archivo):
+ os.remove(ruta_archivo)
+ elif os.path.isdir(ruta_archivo):
+ shutil.rmtree(ruta_archivo)
+
+
+
+ ydl_opts = {
+ 'no-windows-filenames': True,
+ 'restrict-filenames': True,
+ 'extract_audio': True,
+ 'format': 'bestaudio',
+ 'quiet': True,
+ 'no-warnings': True,
+ }
+
+ try:
+ print(i18n("Downloading audio from the video..."))
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
+ info_dict = ydl.extract_info(input_url, download=False)
+ formatted_title = format_title(info_dict.get('title', 'default_title'))
+ formatted_outtmpl = output_path + '/' + formatted_title + '.wav'
+ ydl_opts['outtmpl'] = formatted_outtmpl
+ ydl = yt_dlp.YoutubeDL(ydl_opts)
+ ydl.download([input_url])
+ print(i18n("Audio downloaded!"))
+ except Exception as error:
+ print(i18n("An error occurred:"), error)
+
+ actual_directory = os.path.dirname(__file__)
+
+ vocal_directory = os.path.join(actual_directory, save_root_vocal)
+ instrumental_directory = os.path.join(actual_directory, save_root_ins)
+
+ vocal_formatted = f"vocal_{formatted_title}.wav.reformatted.wav_10.wav"
+ instrumental_formatted = f"instrument_{formatted_title}.wav.reformatted.wav_10.wav"
+
+ vocal_audio_path = os.path.join(vocal_directory, vocal_formatted)
+ instrumental_audio_path = os.path.join(instrumental_directory, instrumental_formatted)
+
+ vocal_formatted_mdx = f"{formatted_title}_vocal_.wav"
+ instrumental_formatted_mdx = f"{formatted_title}_instrument_.wav"
+
+ vocal_audio_path_mdx = os.path.join(vocal_directory, vocal_formatted_mdx)
+ instrumental_audio_path_mdx = os.path.join(instrumental_directory, instrumental_formatted_mdx)
+
+ if architecture == "VR":
+ try:
+ print(i18n("Starting audio conversion... (This might take a moment)"))
+ inp_root, save_root_vocal, save_root_ins = [x.strip(" ").strip('"').strip("\n").strip('"').strip(" ") for x in [inp_root, save_root_vocal, save_root_ins]]
+ usable_files = [os.path.join(inp_root, file)
+ for file in os.listdir(inp_root)
+ if file.endswith(tuple(sup_audioext))]
+
+
+ pre_fun = MDXNetDereverb(15) if model_name == "onnx_dereverb_By_FoxJoy" else (_audio_pre_ if "DeEcho" not in model_name else _audio_pre_new)(
+ agg=int(agg),
+ model_path=os.path.join(weight_uvr5_root, model_name + ".pth"),
+ device=config.device,
+ is_half=config.is_half,
+ )
+
+ try:
+ if paths != None:
+ paths = [path.name for path in paths]
+ else:
+ paths = usable_files
+
+ except:
+ traceback.print_exc()
+ paths = usable_files
+ print(paths)
+ for path in paths:
+ inp_path = os.path.join(inp_root, path)
+ need_reformat, done = 1, 0
+
+ try:
+ info = ffmpeg.probe(inp_path, cmd="ffprobe")
+ if info["streams"][0]["channels"] == 2 and info["streams"][0]["sample_rate"] == "44100":
+ need_reformat = 0
+ pre_fun._path_audio_(inp_path, save_root_ins, save_root_vocal, format0)
+ done = 1
+ except:
+ traceback.print_exc()
+
+ if need_reformat:
+ tmp_path = f"{tmp}/{os.path.basename(inp_path)}.reformatted.wav"
+ os.system(f"ffmpeg -i {inp_path} -vn -acodec pcm_s16le -ac 2 -ar 44100 {tmp_path} -y")
+ inp_path = tmp_path
+
+ try:
+ if not done:
+ pre_fun._path_audio_(inp_path, save_root_ins, save_root_vocal, format0)
+ print(f"{os.path.basename(inp_path)}->Success")
+ except:
+ print(f"{os.path.basename(inp_path)}->{traceback.format_exc()}")
+ except:
+ traceback.print_exc()
+ finally:
+ try:
+ if model_name == "onnx_dereverb_By_FoxJoy":
+ del pre_fun.pred.model
+ del pre_fun.pred.model_
+ else:
+ del pre_fun.model
+
+ del pre_fun
+ return i18n("Finished"), vocal_audio_path, instrumental_audio_path
+ except: traceback.print_exc()
+
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
+
+ elif architecture == "MDX":
+ try:
+ print(i18n("Starting audio conversion... (This might take a moment)"))
+ inp_root, save_root_vocal, save_root_ins = [x.strip(" ").strip('"').strip("\n").strip('"').strip(" ") for x in [inp_root, save_root_vocal, save_root_ins]]
+
+ usable_files = [os.path.join(inp_root, file)
+ for file in os.listdir(inp_root)
+ if file.endswith(tuple(sup_audioext))]
+ try:
+ if paths != None:
+ paths = [path.name for path in paths]
+ else:
+ paths = usable_files
+
+ except:
+ traceback.print_exc()
+ paths = usable_files
+ print(paths)
+ invert=True
+ denoise=True
+ use_custom_parameter=True
+ dim_f=2048
+ dim_t=256
+ n_fft=7680
+ use_custom_compensation=True
+ compensation=1.025
+ suffix = "vocal_" #@param ["Vocals", "Drums", "Bass", "Other"]{allow-input: true}
+ suffix_invert = "instrument_" #@param ["Instrumental", "Drumless", "Bassless", "Instruments"]{allow-input: true}
+ print_settings = True # @param{type:"boolean"}
+ onnx = id_to_ptm(model_name)
+ compensation = compensation if use_custom_compensation or use_custom_parameter else None
+ mdx_model = prepare_mdx(onnx,use_custom_parameter, dim_f, dim_t, n_fft, compensation=compensation)
+
+
+ for path in paths:
+ #inp_path = os.path.join(inp_root, path)
+ suffix_naming = suffix if use_custom_parameter else None
+ diff_suffix_naming = suffix_invert if use_custom_parameter else None
+ run_mdx(onnx, mdx_model, path, format0, diff=invert,suffix=suffix_naming,diff_suffix=diff_suffix_naming,denoise=denoise)
+
+ if print_settings:
+ print()
+ print('[MDX-Net_Colab settings used]')
+ print(f'Model used: {onnx}')
+ print(f'Model MD5: {mdx.MDX.get_hash(onnx)}')
+ print(f'Model parameters:')
+ print(f' -dim_f: {mdx_model.dim_f}')
+ print(f' -dim_t: {mdx_model.dim_t}')
+ print(f' -n_fft: {mdx_model.n_fft}')
+ print(f' -compensation: {mdx_model.compensation}')
+ print()
+ print('[Input file]')
+ print('filename(s): ')
+ for filename in paths:
+ print(f' -{filename}')
+ print(f"{os.path.basename(filename)}->Success")
+ except:
+ traceback.print_exc()
+ finally:
+ try:
+ del mdx_model
+ return i18n("Finished"), vocal_audio_path_mdx, instrumental_audio_path_mdx
+ except: traceback.print_exc()
+
+ print("clean_empty_cache")
+
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
+sup_audioext = {'wav', 'mp3', 'flac', 'ogg', 'opus',
+ 'm4a', 'mp4', 'aac', 'alac', 'wma',
+ 'aiff', 'webm', 'ac3'}
+
+def load_downloaded_audio(url):
+ parent_path = find_folder_parent(".", "pretrained_v2")
+ try:
+ infos = []
+ audios_path = os.path.join(parent_path, 'audios')
+ zips_path = os.path.join(parent_path, 'zips')
+
+ if not os.path.exists(audios_path):
+ os.mkdir(audios_path)
+
+ download_file = download_from_url(url)
+ if not download_file:
+ print(i18n("The file could not be downloaded."))
+ infos.append(i18n("The file could not be downloaded."))
+ yield "\n".join(infos)
+ elif download_file == "downloaded":
+ print(i18n("It has been downloaded successfully."))
+ infos.append(i18n("It has been downloaded successfully."))
+ yield "\n".join(infos)
+ elif download_file == "too much use":
+ raise Exception(i18n("Too many users have recently viewed or downloaded this file"))
+ elif download_file == "private link":
+ raise Exception(i18n("Cannot get file from this private link"))
+
+ for filename in os.listdir(zips_path):
+ item_path = os.path.join(zips_path, filename)
+ if item_path.split('.')[-1] in sup_audioext:
+ if os.path.exists(item_path):
+ shutil.move(item_path, audios_path)
+
+ result = ""
+ print(i18n("Audio files have been moved to the 'audios' folder."))
+ infos.append(i18n("Audio files have been moved to the 'audios' folder."))
+ yield "\n".join(infos)
+
+ os.chdir(parent_path)
+ return result
+ except Exception as e:
+ os.chdir(parent_path)
+ if "too much use" in str(e):
+ print(i18n("Too many users have recently viewed or downloaded this file"))
+ yield i18n("Too many users have recently viewed or downloaded this file")
+ elif "private link" in str(e):
+ print(i18n("Cannot get file from this private link"))
+ yield i18n("Cannot get file from this private link")
+ else:
+ print(e)
+ yield i18n("An error occurred downloading")
+ finally:
+ os.chdir(parent_path)
+
+
+class error_message(Exception):
+ def __init__(self, mensaje):
+ self.mensaje = mensaje
+ super().__init__(mensaje)
+
+def get_vc(sid, to_return_protect0, to_return_protect1):
+ global n_spk, tgt_sr, net_g, vc, cpt, version
+ if sid == "" or sid == []:
+ global hubert_model
+ if hubert_model is not None:
+ print("clean_empty_cache")
+ del net_g, n_spk, vc, hubert_model, tgt_sr
+ hubert_model = net_g = n_spk = vc = hubert_model = tgt_sr = None
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+ if_f0 = cpt.get("f0", 1)
+ version = cpt.get("version", "v1")
+ if version == "v1":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs256NSFsid(
+ *cpt["config"], is_half=config.is_half
+ )
+ else:
+ net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"])
+ elif version == "v2":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs768NSFsid(
+ *cpt["config"], is_half=config.is_half
+ )
+ else:
+ net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"])
+ del net_g, cpt
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+ cpt = None
+ return (
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ {"visible": False, "__type__": "update"},
+ )
+ person = "%s/%s" % (weight_root, sid)
+ print("loading %s" % person)
+ cpt = torch.load(person, map_location="cpu")
+ tgt_sr = cpt["config"][-1]
+ cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0]
+ if_f0 = cpt.get("f0", 1)
+ if if_f0 == 0:
+ to_return_protect0 = to_return_protect1 = {
+ "visible": False,
+ "value": 0.5,
+ "__type__": "update",
+ }
+ else:
+ to_return_protect0 = {
+ "visible": True,
+ "value": to_return_protect0,
+ "__type__": "update",
+ }
+ to_return_protect1 = {
+ "visible": True,
+ "value": to_return_protect1,
+ "__type__": "update",
+ }
+ version = cpt.get("version", "v1")
+ if version == "v1":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs256NSFsid(*cpt["config"], is_half=config.is_half)
+ else:
+ net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"])
+ elif version == "v2":
+ if if_f0 == 1:
+ net_g = SynthesizerTrnMs768NSFsid(*cpt["config"], is_half=config.is_half)
+ else:
+ net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"])
+ del net_g.enc_q
+ print(net_g.load_state_dict(cpt["weight"], strict=False))
+ net_g.eval().to(config.device)
+ if config.is_half:
+ net_g = net_g.half()
+ else:
+ net_g = net_g.float()
+ vc = VC(tgt_sr, config)
+ n_spk = cpt["config"][-3]
+ return (
+ {"visible": True, "maximum": n_spk, "__type__": "update"},
+ to_return_protect0,
+ to_return_protect1,
+ )
+
+def update_model_choices(select_value):
+ model_ids = get_model_list()
+ model_ids_list = list(model_ids)
+ if select_value == "VR":
+ return {"choices": uvr5_names, "__type__": "update"}
+ elif select_value == "MDX":
+ return {"choices": model_ids_list, "__type__": "update"}
+
+def download_model():
+ gr.Markdown(value="# " + i18n("Download Model"))
+ gr.Markdown(value=i18n("It is used to download your inference models."))
+ with gr.Row():
+ model_url=gr.Textbox(label=i18n("Url:"))
+ with gr.Row():
+ download_model_status_bar=gr.Textbox(label=i18n("Status:"))
+ with gr.Row():
+ download_button=gr.Button(i18n("Download"))
+ download_button.click(fn=load_downloaded_model, inputs=[model_url], outputs=[download_model_status_bar])
+
+def download_backup():
+ gr.Markdown(value="# " + i18n("Download Backup"))
+ gr.Markdown(value=i18n("It is used to download your training backups."))
+ with gr.Row():
+ model_url=gr.Textbox(label=i18n("Url:"))
+ with gr.Row():
+ download_model_status_bar=gr.Textbox(label=i18n("Status:"))
+ with gr.Row():
+ download_button=gr.Button(i18n("Download"))
+ download_button.click(fn=load_downloaded_backup, inputs=[model_url], outputs=[download_model_status_bar])
+
+def update_dataset_list(name):
+ new_datasets = []
+ for foldername in os.listdir("./datasets"):
+ if "." not in foldername:
+ new_datasets.append(os.path.join(find_folder_parent(".","pretrained"),"datasets",foldername))
+ return gr.Dropdown.update(choices=new_datasets)
+
+def download_dataset(trainset_dir4):
+ gr.Markdown(value="# " + i18n("Download Dataset"))
+ gr.Markdown(value=i18n("Download the dataset with the audios in a compatible format (.wav/.flac) to train your model."))
+ with gr.Row():
+ dataset_url=gr.Textbox(label=i18n("Url:"))
+ with gr.Row():
+ load_dataset_status_bar=gr.Textbox(label=i18n("Status:"))
+ with gr.Row():
+ load_dataset_button=gr.Button(i18n("Download"))
+ load_dataset_button.click(fn=load_dowloaded_dataset, inputs=[dataset_url], outputs=[load_dataset_status_bar])
+ load_dataset_status_bar.change(update_dataset_list, dataset_url, trainset_dir4)
+
+def download_audio():
+ gr.Markdown(value="# " + i18n("Download Audio"))
+ gr.Markdown(value=i18n("Download audios of any format for use in inference (recommended for mobile users)."))
+ with gr.Row():
+ audio_url=gr.Textbox(label=i18n("Url:"))
+ with gr.Row():
+ download_audio_status_bar=gr.Textbox(label=i18n("Status:"))
+ with gr.Row():
+ download_button2=gr.Button(i18n("Download"))
+ download_button2.click(fn=load_downloaded_audio, inputs=[audio_url], outputs=[download_audio_status_bar])
+
+def youtube_separator():
+ gr.Markdown(value="# " + i18n("Separate YouTube tracks"))
+ gr.Markdown(value=i18n("Download audio from a YouTube video and automatically separate the vocal and instrumental tracks"))
+ with gr.Row():
+ input_url = gr.inputs.Textbox(label=i18n("Enter the YouTube link:"))
+ output_path = gr.Textbox(
+ label=i18n("Enter the path of the audio folder to be processed (copy it from the address bar of the file manager):"),
+ value=os.path.abspath(os.getcwd()).replace('\\', '/') + "/yt_downloads",
+ visible=False,
+ )
+ advanced_settings_checkbox = gr.Checkbox(
+ value=False,
+ label=i18n("Advanced Settings"),
+ interactive=True,
+ )
+ with gr.Row(label = i18n("Advanced Settings"), visible=False, variant='compact') as advanced_settings:
+ with gr.Column():
+ model_select = gr.Radio(
+ label=i18n("Model Architecture:"),
+ choices=["VR", "MDX"],
+ value="VR",
+ interactive=True,
+ )
+ model_choose = gr.Dropdown(label=i18n("Model: (Be aware that in some models the named vocal will be the instrumental)"),
+ choices=uvr5_names,
+ value="HP5_only_main_vocal"
+ )
+ with gr.Row():
+ agg = gr.Slider(
+ minimum=0,
+ maximum=20,
+ step=1,
+ label=i18n("Vocal Extraction Aggressive"),
+ value=10,
+ interactive=True,
+ )
+ with gr.Row():
+ opt_vocal_root = gr.Textbox(
+ label=i18n("Specify the output folder for vocals:"), value="audios",
+ )
+ opt_ins_root = gr.Textbox(
+ label=i18n("Specify the output folder for accompaniment:"), value="audio-others",
+ )
+ dir_wav_input = gr.Textbox(
+ label=i18n("Enter the path of the audio folder to be processed:"),
+ value=((os.getcwd()).replace('\\', '/') + "/yt_downloads"),
+ visible=False,
+ )
+ format0 = gr.Radio(
+ label=i18n("Export file format"),
+ choices=["wav", "flac", "mp3", "m4a"],
+ value="wav",
+ visible=False,
+ interactive=True,
+ )
+ wav_inputs = gr.File(
+ file_count="multiple", label=i18n("You can also input audio files in batches. Choose one of the two options. Priority is given to reading from the folder."),
+ visible=False,
+ )
+ model_select.change(
+ fn=update_model_choices,
+ inputs=model_select,
+ outputs=model_choose,
+ )
+ with gr.Row():
+ vc_output4 = gr.Textbox(label=i18n("Status:"))
+ vc_output5 = gr.Audio(label=i18n("Vocal"), type='filepath')
+ vc_output6 = gr.Audio(label=i18n("Instrumental"), type='filepath')
+ with gr.Row():
+ but2 = gr.Button(i18n("Download and Separate"))
+ but2.click(
+ uvr,
+ [
+ input_url,
+ output_path,
+ model_choose,
+ dir_wav_input,
+ opt_vocal_root,
+ wav_inputs,
+ opt_ins_root,
+ agg,
+ format0,
+ model_select
+ ],
+ [vc_output4, vc_output5, vc_output6],
+ )
+ def toggle_advanced_settings(checkbox):
+ return {"visible": checkbox, "__type__": "update"}
+
+ advanced_settings_checkbox.change(
+ fn=toggle_advanced_settings,
+ inputs=[advanced_settings_checkbox],
+ outputs=[advanced_settings]
+ )
+
+
+def get_bark_voice():
+ mensaje = """
+v2/en_speaker_0 English Male
+v2/en_speaker_1 English Male
+v2/en_speaker_2 English Male
+v2/en_speaker_3 English Male
+v2/en_speaker_4 English Male
+v2/en_speaker_5 English Male
+v2/en_speaker_6 English Male
+v2/en_speaker_7 English Male
+v2/en_speaker_8 English Male
+v2/en_speaker_9 English Female
+v2/zh_speaker_0 Chinese (Simplified) Male
+v2/zh_speaker_1 Chinese (Simplified) Male
+v2/zh_speaker_2 Chinese (Simplified) Male
+v2/zh_speaker_3 Chinese (Simplified) Male
+v2/zh_speaker_4 Chinese (Simplified) Female
+v2/zh_speaker_5 Chinese (Simplified) Male
+v2/zh_speaker_6 Chinese (Simplified) Female
+v2/zh_speaker_7 Chinese (Simplified) Female
+v2/zh_speaker_8 Chinese (Simplified) Male
+v2/zh_speaker_9 Chinese (Simplified) Female
+v2/fr_speaker_0 French Male
+v2/fr_speaker_1 French Female
+v2/fr_speaker_2 French Female
+v2/fr_speaker_3 French Male
+v2/fr_speaker_4 French Male
+v2/fr_speaker_5 French Female
+v2/fr_speaker_6 French Male
+v2/fr_speaker_7 French Male
+v2/fr_speaker_8 French Male
+v2/fr_speaker_9 French Male
+v2/de_speaker_0 German Male
+v2/de_speaker_1 German Male
+v2/de_speaker_2 German Male
+v2/de_speaker_3 German Female
+v2/de_speaker_4 German Male
+v2/de_speaker_5 German Male
+v2/de_speaker_6 German Male
+v2/de_speaker_7 German Male
+v2/de_speaker_8 German Female
+v2/de_speaker_9 German Male
+v2/hi_speaker_0 Hindi Female
+v2/hi_speaker_1 Hindi Female
+v2/hi_speaker_2 Hindi Male
+v2/hi_speaker_3 Hindi Female
+v2/hi_speaker_4 Hindi Female
+v2/hi_speaker_5 Hindi Male
+v2/hi_speaker_6 Hindi Male
+v2/hi_speaker_7 Hindi Male
+v2/hi_speaker_8 Hindi Male
+v2/hi_speaker_9 Hindi Female
+v2/it_speaker_0 Italian Male
+v2/it_speaker_1 Italian Male
+v2/it_speaker_2 Italian Female
+v2/it_speaker_3 Italian Male
+v2/it_speaker_4 Italian Male
+v2/it_speaker_5 Italian Male
+v2/it_speaker_6 Italian Male
+v2/it_speaker_7 Italian Female
+v2/it_speaker_8 Italian Male
+v2/it_speaker_9 Italian Female
+v2/ja_speaker_0 Japanese Female
+v2/ja_speaker_1 Japanese Female
+v2/ja_speaker_2 Japanese Male
+v2/ja_speaker_3 Japanese Female
+v2/ja_speaker_4 Japanese Female
+v2/ja_speaker_5 Japanese Female
+v2/ja_speaker_6 Japanese Male
+v2/ja_speaker_7 Japanese Female
+v2/ja_speaker_8 Japanese Female
+v2/ja_speaker_9 Japanese Female
+v2/ko_speaker_0 Korean Female
+v2/ko_speaker_1 Korean Male
+v2/ko_speaker_2 Korean Male
+v2/ko_speaker_3 Korean Male
+v2/ko_speaker_4 Korean Male
+v2/ko_speaker_5 Korean Male
+v2/ko_speaker_6 Korean Male
+v2/ko_speaker_7 Korean Male
+v2/ko_speaker_8 Korean Male
+v2/ko_speaker_9 Korean Male
+v2/pl_speaker_0 Polish Male
+v2/pl_speaker_1 Polish Male
+v2/pl_speaker_2 Polish Male
+v2/pl_speaker_3 Polish Male
+v2/pl_speaker_4 Polish Female
+v2/pl_speaker_5 Polish Male
+v2/pl_speaker_6 Polish Female
+v2/pl_speaker_7 Polish Male
+v2/pl_speaker_8 Polish Male
+v2/pl_speaker_9 Polish Female
+v2/pt_speaker_0 Portuguese Male
+v2/pt_speaker_1 Portuguese Male
+v2/pt_speaker_2 Portuguese Male
+v2/pt_speaker_3 Portuguese Male
+v2/pt_speaker_4 Portuguese Male
+v2/pt_speaker_5 Portuguese Male
+v2/pt_speaker_6 Portuguese Male
+v2/pt_speaker_7 Portuguese Male
+v2/pt_speaker_8 Portuguese Male
+v2/pt_speaker_9 Portuguese Male
+v2/ru_speaker_0 Russian Male
+v2/ru_speaker_1 Russian Male
+v2/ru_speaker_2 Russian Male
+v2/ru_speaker_3 Russian Male
+v2/ru_speaker_4 Russian Male
+v2/ru_speaker_5 Russian Female
+v2/ru_speaker_6 Russian Female
+v2/ru_speaker_7 Russian Male
+v2/ru_speaker_8 Russian Male
+v2/ru_speaker_9 Russian Female
+v2/es_speaker_0 Spanish Male
+v2/es_speaker_1 Spanish Male
+v2/es_speaker_2 Spanish Male
+v2/es_speaker_3 Spanish Male
+v2/es_speaker_4 Spanish Male
+v2/es_speaker_5 Spanish Male
+v2/es_speaker_6 Spanish Male
+v2/es_speaker_7 Spanish Male
+v2/es_speaker_8 Spanish Female
+v2/es_speaker_9 Spanish Female
+v2/tr_speaker_0 Turkish Male
+v2/tr_speaker_1 Turkish Male
+v2/tr_speaker_2 Turkish Male
+v2/tr_speaker_3 Turkish Male
+v2/tr_speaker_4 Turkish Female
+v2/tr_speaker_5 Turkish Female
+v2/tr_speaker_6 Turkish Male
+v2/tr_speaker_7 Turkish Male
+v2/tr_speaker_8 Turkish Male
+v2/tr_speaker_9 Turkish Male
+ """
+# Dividir el mensaje en líneas
+ lineas = mensaje.split("\n")
+ datos_deseados = []
+ for linea in lineas:
+ partes = linea.split("\t")
+ if len(partes) == 3:
+ clave, _, genero = partes
+ datos_deseados.append(f"{clave}-{genero}")
+
+ return datos_deseados
+
+
+def get_edge_voice():
+ completed_process = subprocess.run(['edge-tts',"-l"], capture_output=True, text=True)
+ lines = completed_process.stdout.strip().split("\n")
+ data = []
+ current_entry = {}
+ for line in lines:
+ if line.startswith("Name: "):
+ if current_entry:
+ data.append(current_entry)
+ current_entry = {"Name": line.split(": ")[1]}
+ elif line.startswith("Gender: "):
+ current_entry["Gender"] = line.split(": ")[1]
+ if current_entry:
+ data.append(current_entry)
+ tts_voice = []
+ for entry in data:
+ name = entry["Name"]
+ gender = entry["Gender"]
+ formatted_entry = f'{name}-{gender}'
+ tts_voice.append(formatted_entry)
+ return tts_voice
+
+
+#print(set_tts_voice)
diff --git a/environment_dml.yaml b/environment_dml.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0fb3f222554eb01acce5313bf81cee4179edf0af
--- /dev/null
+++ b/environment_dml.yaml
@@ -0,0 +1,186 @@
+name: pydml
+channels:
+ - pytorch
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
+ - defaults
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/fastai/
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
+ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
+dependencies:
+ - abseil-cpp=20211102.0=hd77b12b_0
+ - absl-py=1.3.0=py310haa95532_0
+ - aiohttp=3.8.3=py310h2bbff1b_0
+ - aiosignal=1.2.0=pyhd3eb1b0_0
+ - async-timeout=4.0.2=py310haa95532_0
+ - attrs=22.1.0=py310haa95532_0
+ - blas=1.0=mkl
+ - blinker=1.4=py310haa95532_0
+ - bottleneck=1.3.5=py310h9128911_0
+ - brotli=1.0.9=h2bbff1b_7
+ - brotli-bin=1.0.9=h2bbff1b_7
+ - brotlipy=0.7.0=py310h2bbff1b_1002
+ - bzip2=1.0.8=he774522_0
+ - c-ares=1.19.0=h2bbff1b_0
+ - ca-certificates=2023.05.30=haa95532_0
+ - cachetools=4.2.2=pyhd3eb1b0_0
+ - certifi=2023.5.7=py310haa95532_0
+ - cffi=1.15.1=py310h2bbff1b_3
+ - charset-normalizer=2.0.4=pyhd3eb1b0_0
+ - click=8.0.4=py310haa95532_0
+ - colorama=0.4.6=py310haa95532_0
+ - contourpy=1.0.5=py310h59b6b97_0
+ - cryptography=39.0.1=py310h21b164f_0
+ - cycler=0.11.0=pyhd3eb1b0_0
+ - fonttools=4.25.0=pyhd3eb1b0_0
+ - freetype=2.12.1=ha860e81_0
+ - frozenlist=1.3.3=py310h2bbff1b_0
+ - giflib=5.2.1=h8cc25b3_3
+ - glib=2.69.1=h5dc1a3c_2
+ - google-auth=2.6.0=pyhd3eb1b0_0
+ - google-auth-oauthlib=0.4.4=pyhd3eb1b0_0
+ - grpc-cpp=1.48.2=hf108199_0
+ - grpcio=1.48.2=py310hf108199_0
+ - gst-plugins-base=1.18.5=h9e645db_0
+ - gstreamer=1.18.5=hd78058f_0
+ - icu=58.2=ha925a31_3
+ - idna=3.4=py310haa95532_0
+ - intel-openmp=2023.1.0=h59b6b97_46319
+ - jpeg=9e=h2bbff1b_1
+ - kiwisolver=1.4.4=py310hd77b12b_0
+ - krb5=1.19.4=h5b6d351_0
+ - lerc=3.0=hd77b12b_0
+ - libbrotlicommon=1.0.9=h2bbff1b_7
+ - libbrotlidec=1.0.9=h2bbff1b_7
+ - libbrotlienc=1.0.9=h2bbff1b_7
+ - libclang=14.0.6=default_hb5a9fac_1
+ - libclang13=14.0.6=default_h8e68704_1
+ - libdeflate=1.17=h2bbff1b_0
+ - libffi=3.4.4=hd77b12b_0
+ - libiconv=1.16=h2bbff1b_2
+ - libogg=1.3.5=h2bbff1b_1
+ - libpng=1.6.39=h8cc25b3_0
+ - libprotobuf=3.20.3=h23ce68f_0
+ - libtiff=4.5.0=h6c2663c_2
+ - libuv=1.44.2=h2bbff1b_0
+ - libvorbis=1.3.7=he774522_0
+ - libwebp=1.2.4=hbc33d0d_1
+ - libwebp-base=1.2.4=h2bbff1b_1
+ - libxml2=2.10.3=h0ad7f3c_0
+ - libxslt=1.1.37=h2bbff1b_0
+ - lz4-c=1.9.4=h2bbff1b_0
+ - markdown=3.4.1=py310haa95532_0
+ - markupsafe=2.1.1=py310h2bbff1b_0
+ - matplotlib=3.7.1=py310haa95532_1
+ - matplotlib-base=3.7.1=py310h4ed8f06_1
+ - mkl=2023.1.0=h8bd8f75_46356
+ - mkl-service=2.4.0=py310h2bbff1b_1
+ - mkl_fft=1.3.6=py310h4ed8f06_1
+ - mkl_random=1.2.2=py310h4ed8f06_1
+ - multidict=6.0.2=py310h2bbff1b_0
+ - munkres=1.1.4=py_0
+ - numexpr=2.8.4=py310h2cd9be0_1
+ - numpy=1.24.3=py310h055cbcc_1
+ - numpy-base=1.24.3=py310h65a83cf_1
+ - oauthlib=3.2.2=py310haa95532_0
+ - openssl=1.1.1t=h2bbff1b_0
+ - packaging=23.0=py310haa95532_0
+ - pandas=1.5.3=py310h4ed8f06_0
+ - pcre=8.45=hd77b12b_0
+ - pillow=9.4.0=py310hd77b12b_0
+ - pip=23.0.1=py310haa95532_0
+ - ply=3.11=py310haa95532_0
+ - protobuf=3.20.3=py310hd77b12b_0
+ - pyasn1=0.4.8=pyhd3eb1b0_0
+ - pyasn1-modules=0.2.8=py_0
+ - pycparser=2.21=pyhd3eb1b0_0
+ - pyjwt=2.4.0=py310haa95532_0
+ - pyopenssl=23.0.0=py310haa95532_0
+ - pyparsing=3.0.9=py310haa95532_0
+ - pyqt=5.15.7=py310hd77b12b_0
+ - pyqt5-sip=12.11.0=py310hd77b12b_0
+ - pysocks=1.7.1=py310haa95532_0
+ - python=3.10.11=h966fe2a_2
+ - python-dateutil=2.8.2=pyhd3eb1b0_0
+ - pytorch-mutex=1.0=cpu
+ - pytz=2022.7=py310haa95532_0
+ - pyyaml=6.0=py310h2bbff1b_1
+ - qt-main=5.15.2=he8e5bd7_8
+ - qt-webengine=5.15.9=hb9a9bb5_5
+ - qtwebkit=5.212=h2bbfb41_5
+ - re2=2022.04.01=hd77b12b_0
+ - requests=2.29.0=py310haa95532_0
+ - requests-oauthlib=1.3.0=py_0
+ - rsa=4.7.2=pyhd3eb1b0_1
+ - setuptools=67.8.0=py310haa95532_0
+ - sip=6.6.2=py310hd77b12b_0
+ - six=1.16.0=pyhd3eb1b0_1
+ - sqlite=3.41.2=h2bbff1b_0
+ - tbb=2021.8.0=h59b6b97_0
+ - tensorboard=2.10.0=py310haa95532_0
+ - tensorboard-data-server=0.6.1=py310haa95532_0
+ - tensorboard-plugin-wit=1.8.1=py310haa95532_0
+ - tk=8.6.12=h2bbff1b_0
+ - toml=0.10.2=pyhd3eb1b0_0
+ - tornado=6.2=py310h2bbff1b_0
+ - tqdm=4.65.0=py310h9909e9c_0
+ - typing_extensions=4.5.0=py310haa95532_0
+ - tzdata=2023c=h04d1e81_0
+ - urllib3=1.26.16=py310haa95532_0
+ - vc=14.2=h21ff451_1
+ - vs2015_runtime=14.27.29016=h5e58377_2
+ - werkzeug=2.2.3=py310haa95532_0
+ - wheel=0.38.4=py310haa95532_0
+ - win_inet_pton=1.1.0=py310haa95532_0
+ - xz=5.4.2=h8cc25b3_0
+ - yaml=0.2.5=he774522_0
+ - yarl=1.8.1=py310h2bbff1b_0
+ - zlib=1.2.13=h8cc25b3_0
+ - zstd=1.5.5=hd43e919_0
+ - pip:
+ - antlr4-python3-runtime==4.8
+ - appdirs==1.4.4
+ - audioread==3.0.0
+ - bitarray==2.7.4
+ - cython==0.29.35
+ - decorator==5.1.1
+ - fairseq==0.12.2
+ - faiss-cpu==1.7.4
+ - filelock==3.12.0
+ - hydra-core==1.0.7
+ - jinja2==3.1.2
+ - joblib==1.2.0
+ - lazy-loader==0.2
+ - librosa==0.10.0.post2
+ - llvmlite==0.40.0
+ - lxml==4.9.2
+ - mpmath==1.3.0
+ - msgpack==1.0.5
+ - networkx==3.1
+ - noisereduce==2.0.1
+ - numba==0.57.0
+ - omegaconf==2.0.6
+ - opencv-python==4.7.0.72
+ - pooch==1.6.0
+ - portalocker==2.7.0
+ - pysimplegui==4.60.5
+ - pywin32==306
+ - pyworld==0.3.3
+ - regex==2023.5.5
+ - sacrebleu==2.3.1
+ - scikit-learn==1.2.2
+ - scipy==1.10.1
+ - sounddevice==0.4.6
+ - soundfile==0.12.1
+ - soxr==0.3.5
+ - sympy==1.12
+ - tabulate==0.9.0
+ - threadpoolctl==3.1.0
+ - torch==2.0.0
+ - torch-directml==0.2.0.dev230426
+ - torchaudio==2.0.1
+ - torchvision==0.15.1
+ - wget==3.2
+prefix: D:\ProgramData\anaconda3_\envs\pydml
diff --git a/extract_locale.py b/extract_locale.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4ff5ea3ddd7c612c640544099ab98a861b8fe35
--- /dev/null
+++ b/extract_locale.py
@@ -0,0 +1,34 @@
+import json
+import re
+
+# Define regular expression patterns
+pattern = r"""i18n\([\s\n\t]*(["'][^"']+["'])[\s\n\t]*\)"""
+
+# Initialize the dictionary to store key-value pairs
+data = {}
+
+
+def process(fn: str):
+ global data
+ with open(fn, "r", encoding="utf-8") as f:
+ contents = f.read()
+ matches = re.findall(pattern, contents)
+ for key in matches:
+ key = eval(key)
+ print("extract:", key)
+ data[key] = key
+
+
+print("processing infer-web.py")
+process("infer-web.py")
+
+print("processing gui_v0.py")
+process("gui_v0.py")
+
+print("processing gui_v1.py")
+process("gui_v1.py")
+
+# Save as a JSON file
+with open("./i18n/en_US.json", "w", encoding="utf-8") as f:
+ json.dump(data, f, ensure_ascii=False, indent=4)
+ f.write("\n")
diff --git a/formantshiftcfg/Put your formantshift presets here as a txt file b/formantshiftcfg/Put your formantshift presets here as a txt file
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/formantshiftcfg/f2m.txt b/formantshiftcfg/f2m.txt
new file mode 100644
index 0000000000000000000000000000000000000000..40356a80ce7dd7a893bca233a41306525193f2f0
--- /dev/null
+++ b/formantshiftcfg/f2m.txt
@@ -0,0 +1,2 @@
+1.0
+0.8
\ No newline at end of file
diff --git a/formantshiftcfg/m2f.txt b/formantshiftcfg/m2f.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fa69b52dc8e29db4697401ef2766dd0b6c6f4b47
--- /dev/null
+++ b/formantshiftcfg/m2f.txt
@@ -0,0 +1,2 @@
+1.0
+1.2
\ No newline at end of file
diff --git a/formantshiftcfg/random.txt b/formantshiftcfg/random.txt
new file mode 100644
index 0000000000000000000000000000000000000000..427be5c80412098ecec082b3a06e867ddc9a7ba2
--- /dev/null
+++ b/formantshiftcfg/random.txt
@@ -0,0 +1,2 @@
+32.0
+9.8
\ No newline at end of file
diff --git a/get-pip.py b/get-pip.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf68a2b3426decf01bde021a50c47a2115af303a
--- /dev/null
+++ b/get-pip.py
@@ -0,0 +1,32657 @@
+#!/usr/bin/env python
+#
+# Hi There!
+#
+# You may be wondering what this giant blob of binary data here is, you might
+# even be worried that we're up to something nefarious (good for you for being
+# paranoid!). This is a base85 encoding of a zip file, this zip file contains
+# an entire copy of pip (version 23.2.1).
+#
+# Pip is a thing that installs packages, pip itself is a package that someone
+# might want to install, especially if they're looking to run this get-pip.py
+# script. Pip has a lot of code to deal with the security of installing
+# packages, various edge cases on various platforms, and other such sort of
+# "tribal knowledge" that has been encoded in its code base. Because of this
+# we basically include an entire copy of pip inside this blob. We do this
+# because the alternatives are attempt to implement a "minipip" that probably
+# doesn't do things correctly and has weird edge cases, or compress pip itself
+# down into a single file.
+#
+# If you're wondering how this is created, it is generated using
+# `scripts/generate.py` in https://github.com/pypa/get-pip.
+
+import sys
+
+this_python = sys.version_info[:2]
+min_version = (3, 7)
+if this_python < min_version:
+ message_parts = [
+ "This script does not work on Python {}.{}".format(*this_python),
+ "The minimum supported Python version is {}.{}.".format(*min_version),
+ "Please use https://bootstrap.pypa.io/pip/{}.{}/get-pip.py instead.".format(*this_python),
+ ]
+ print("ERROR: " + " ".join(message_parts))
+ sys.exit(1)
+
+
+import os.path
+import pkgutil
+import shutil
+import tempfile
+import argparse
+import importlib
+from base64 import b85decode
+
+
+def include_setuptools(args):
+ """
+ Install setuptools only if absent and not excluded.
+ """
+ cli = not args.no_setuptools
+ env = not os.environ.get("PIP_NO_SETUPTOOLS")
+ absent = not importlib.util.find_spec("setuptools")
+ return cli and env and absent
+
+
+def include_wheel(args):
+ """
+ Install wheel only if absent and not excluded.
+ """
+ cli = not args.no_wheel
+ env = not os.environ.get("PIP_NO_WHEEL")
+ absent = not importlib.util.find_spec("wheel")
+ return cli and env and absent
+
+
+def determine_pip_install_arguments():
+ pre_parser = argparse.ArgumentParser()
+ pre_parser.add_argument("--no-setuptools", action="store_true")
+ pre_parser.add_argument("--no-wheel", action="store_true")
+ pre, args = pre_parser.parse_known_args()
+
+ args.append("pip")
+
+ if include_setuptools(pre):
+ args.append("setuptools")
+
+ if include_wheel(pre):
+ args.append("wheel")
+
+ return ["install", "--upgrade", "--force-reinstall"] + args
+
+
+def monkeypatch_for_cert(tmpdir):
+ """Patches `pip install` to provide default certificate with the lowest priority.
+
+ This ensures that the bundled certificates are used unless the user specifies a
+ custom cert via any of pip's option passing mechanisms (config, env-var, CLI).
+
+ A monkeypatch is the easiest way to achieve this, without messing too much with
+ the rest of pip's internals.
+ """
+ from pip._internal.commands.install import InstallCommand
+
+ # We want to be using the internal certificates.
+ cert_path = os.path.join(tmpdir, "cacert.pem")
+ with open(cert_path, "wb") as cert:
+ cert.write(pkgutil.get_data("pip._vendor.certifi", "cacert.pem"))
+
+ install_parse_args = InstallCommand.parse_args
+
+ def cert_parse_args(self, args):
+ if not self.parser.get_default_values().cert:
+ # There are no user provided cert -- force use of bundled cert
+ self.parser.defaults["cert"] = cert_path # calculated above
+ return install_parse_args(self, args)
+
+ InstallCommand.parse_args = cert_parse_args
+
+
+def bootstrap(tmpdir):
+ monkeypatch_for_cert(tmpdir)
+
+ # Execute the included pip and use it to install the latest pip and
+ # setuptools from PyPI
+ from pip._internal.cli.main import main as pip_entry_point
+ args = determine_pip_install_arguments()
+ sys.exit(pip_entry_point(args))
+
+
+def main():
+ tmpdir = None
+ try:
+ # Create a temporary working directory
+ tmpdir = tempfile.mkdtemp()
+
+ # Unpack the zipfile into the temporary directory
+ pip_zip = os.path.join(tmpdir, "pip.zip")
+ with open(pip_zip, "wb") as fp:
+ fp.write(b85decode(DATA.replace(b"\n", b"")))
+
+ # Add the zipfile to sys.path so that we can import it
+ sys.path.insert(0, pip_zip)
+
+ # Run the bootstrap
+ bootstrap(tmpdir=tmpdir)
+ finally:
+ # Clean up our temporary working directory
+ if tmpdir:
+ shutil.rmtree(tmpdir, ignore_errors=True)
+
+
+DATA = b"""
+P)h>@6aWAK2ml*O_EvJ33*7hs003nH000jF003}la4%n9X>MtBUtcb8c|B0UO2j}6z0X&KUUXrd;wrc
+n6ubz6s0VM$QfAw<4YV^ulDhQoop$MlK*;0e$L01LzdVw?IP-tnf*qTlkJj!Mom=viw7qw3H>hK(>
+3ZJA0oQV`^+*aO7_tw^Cd$4zs{Pl#j>6{|X*AaQ6!2wJ?w>%d+2&1X4Rc!^r6h-hMtH_d5{IF3D`nKTt~p1QY-O00;mZO7>Q7_pjHy0RRA2
+0{{RI0001RX>c!JUu|J&ZeL$6aCu!)OK;mS48HqU5b43r;JP^vOMxACEp{6QLy+m1h%E`C9MAjpBNe-
+8r;{H19{ebpf{zJ27j)n8%0=-6Z#elILRo@w9oRWWbO{z8ujDS!QAC@3T%nJCf;1rX6ghzu#Z}R@K&*?Hgj1WFD91+adaM4G`4Xs@*hA^t@nbDYdL)-aOjsW~3}QVVby(8=@7U$
+Fzj5Y{w!2hUUH`?e9j7WDA;>-1aos>7j{2$~BfyL8p@__Y98dsP#Bs7^lWF
+=e_gr;(4^?am?Cp93+7b-!?~nb}-$cPSR1zckA*zNp!)$;YjlZrfn&RWNM}=QA7*cb8A{(9@{5!vBfq
+rEMoeu5FvJZngI@N#4#(2v$WnMGCAVD?b9t8W^qDfcFBe5ZZF%dPAPaq#>ikclG~yPvCg`JUGb_W2#PdCXxx}7!|T*xc9qdnTILbO-nAJaF2
+~0snMFDU<%E01X4*yW9@|}F2;vY~;0|XQR000O88%p+8eg`F$&;kGeqy+!~6#xJLaA|NaUte%(a4
+m9mZf<3AUtcb8d3{vDZrd;nz56RT_b?l9y`mj3AXtV0MT+&(WJ!7$x|Xq_^eh*q`
+qYNbl$TgcX!{RW4b=Vw*pI`moV*K|DJ2bY*KQViviHGglIK{X_)>pN=IEr427|<0g`vfCSX-CrF6hnx-
+fU6^LzLVM{GttvQ!RX(K-@qvQ<9nZh3{TwCd*xxj~wep|+d4YrpRGd3uJ(;$x#MJ^wO(dX9-I(W~SOL
+|!j@ev4#PBd+t2O-3Y4TDlA%@&y9h}l?d7(gvc*a&O+atWdOv5|
+XtFg8N1I1Eg2~6T^Prn{|GZSIw2~Ql9c?>!a3=lwO6eT!TZzV{RAoH`=gPAEk0OKF^-L_LxAV)%Ld>V
+rC7Ea!84dqJ@cSb~%=6Dm=^V^deci#%k)qhs`k`mikNs;GRv|TRB1+w&XWHK8?pSmvO+Mn5HP0Rg&
+0e2!{O&s!2A%Oz`W5|6)QOoeMptG0vVbf-p%MA<(l*rGUrRG$G|nf0000U0RR9D
+0001RX>c!ac`kH$aAjmAj&U%1)EvFVxRjSzi=C>J@cM
+k87yJyz4~-Qcqlg}hXv}1CF`fEox?~SG{pae%Dy$pBG>tnWs3{>FohpTZSG@fe-hAmws@4PFv7Mv`H@
+JnAXTbgKqwrl)IWaYE>+%OsO9KQH0000802@m7R+hDJp-}+<06hW#02u%P0B~t=FJEbHbY*gGVQep7U
+ukY>bYEXCaCvo+F;B!W42Adn3hP*|5~K?fa1xA6Cs^1JI)&D4jnX98E~x(=w{RdN$P(+xdH(X;aUMbE
+La7HDOJ;>ViJroJQOYSq=f31Z#UCgsvZ;PjisC7~V50}YW@1zhN!C_4fs|i^>lX7r-W?|$V(y(g0ZOD
+x-5bTWf^iasXM`rih^?Sk#%z{jZl{Ri-7?Gn9_p
+NH(fR_VZQx#ZustU5xCHVj%1=)fT*F;XSi#wiQR~iuoy}(RFp&L9pfC#Zn^7Axz>2yIKB7|@~y3-1&J5eC&FySna4hw0fjd92G^LTzc+Br>7Y7w1=({
+s_3<|LwzLQl3jT^=CKlyadUgD7M{+)3>-rRJjjOO9KQH0000802@m7Rtz49V_OUW00Srh02%-Q0B~t=
+FJEbHbY*gGVQepAb!lv5UuAA~E^v9(T1#`|xDmeVS714ZB@>eSIHgphB=gYhxH9p$W;~m0sZ?Bwghq@
+hk_(WwwJ!hnbT%XT~X$2UELOWbx^D5}
+p)=7nt84rjpQ!t=bvqBu6SXjxf*{)}V#v6kjnleUMl*qKLJw7ma)>Zw|O-`#U0v?-c6x#d+}i#X$=E%t?3;qCzPO{p3XDn-l0g8$MLf}@FhxjzhJPffk$LZTb=
+tRL0mAd`8KB>SS|Ny1Wz!%10ZP4l!(Z9X~Qr(M}5e1M{2V-3vl>e`}|vFvt@s535m*|M}OlVSM$)RrHcBrimd6?lFPUdh
+^8oI-}L;caqLRJjDa?_Dr07Ysf#%z>QWYbSDW3_SKrT&dAFG`Lt`@W9KJiJ}-Jm
+Eim0UQMILLA#<&5?}IiA5v%!>tEItSETqsiWmt%EBta_NRXj{H*Zo{ba+L0f#Cr>zURR@B*qHa1TLRl
+QIY3XdTuN;Q8cY|sQ{2jC4o$vPgD13HO~sl#?~l?=&A}cMFP(CNl(yMsR`-t2i}7DFz8rYgPveC_)gi
+?sXaiv@_U|jtx7a74!l@<;4JHe05F%Q2)SdHLgXxn>Gh!i1WT2K^_-Mqe1LMOvU4R{fH+QfQ%eQYa2d
++e#MFwQ*oaQwvhGC2wTnRW_zJ##J9Pw*x1bE%az6lfqS#7Kz)e-Rnn7GhG_W5G{(q)4xvM*rJ>eb1rMlGrLDy?OC^}{4osLlt4f7K8F}Z|`B#E1o*9RQ|@+2V@Bv`<7P)h{}C>a!R4k{Eil{;q0l?#-&mQ~4}M0|c2#OI;L{3Tudz_N!_rY+hTGzghD(#5kNVHprZaZYt##W$uR8%mb^&)N6ivKk8Fogh
+BMr8%*?0wS)XN@6p#nApa&Y-w9Ew#Zu@h&NSzS79U`3ykgq8X+X_$OD4A`np9UQ(OZ&?G>pd7)u100h
+6&Ehk$^P1yyq9_uwBi4bItZ;{YLK4idID%pU;f7}zm-6NU3Bg;MsQ)C_Xl%pd#APfelK6ZX)4MevarN3gu`&(XOy?+-ilBrvl6uD3dNNZ)`pUd=i?WZkc;yu4_~oaIcdwK6<&R*Ivfg2cB}&44
+buBuR0s5klsH#FHwVF%6r=l3b;v1Sm=o@?fr!Fer2uDL9L&_isoatz297jX@o{A}`XCC6WOfkP0%87K
+kvdJZNsFYvO_uCPfDQ#!T$k!x23L!YQl05fIp!Qum#J6eLAwB~uW~Tzhl*@BpO*0iZn3-W@i=nr-W|(
+11w{K!f#O~7gF*~{#IxcX?lmI`bj@X}m2N^O|Q*fI&p?b#N0ES?Pkc+y}Zj6*0Av01gLhWTd
+nOTbhdhE1BKPS3Fg`Z@s&2l@TrvgBPRJC~P2NN1QqdwS}`bs=5XEmp~mF78qqjMEpthH9w@9Bbi4O^<
+&W#%iuEa|8!D0@I9@+SrhW~?;jIcMk1?8@}gUVbulb{fRenF5C)Hq^fLi3aX)oB9={Aw5B1Eya}ud)zI$Kgq)lD
+w*#4Ftggw^aT0%+Vu2)HvSC$KTnIVg4EflgOXhv&oh3ZSz1L>6#^bnq(m1-OmeK*vFp=M92_79T`!l}
+{9`kKpLiSkSXPgGNTXzCM!m$>YmKH1A`kiHiQ*43y-)7dhX|M$wzbh0!*8wWuO
+$+?!aMXV))oSMbZk=4=^~Bzkn$82y9L)OTJZ?WBoqw*oo)B@x+ciJET=1kF<^8cqPG
+P!?R*vWNM|ELa#g+A5(FI=e?5HVwlmM86Sq%vDSn84<7Nu4Cy@v^93Go0~&XH@{(?4R;W-+{wnaSX4h
+dBLaWBKHJuX_r9tZX^)!C5M>y}CCk2Bg3Q180j_`3MbCnUAON=wR_Mw>=B(2!qdobEOu2v5=yT@shGM
+~r3jQ4Lc*S5nc8W7-2G(!r^M~cFZ6m}#W&xX`N#98l}tUwm`Ct`*ss)D%~d2{jaf3BD8RZT}pLU*I=(
+}#C|8y|~WONGYELk8FDK9$3V(nS{-09xll!z%G^#&nYYK%@=^l2j(@kWt-j^soOk{KTUk>+MW2)n^^6
+(IL-fyvERX*?qG*p`hD|5y$?@0$n)X&O2H;^6Ex)SV+1jP-txm+iOw9lzzJAF$`cMda)C%T
+GVJkVYGtLqIROwK@kZ{Ay>IU@jDOX%0SdYm|x;oqbm2$vloyp_(hz1t7I43OljOG#o7wOvTf?rAeARa
+|#tj9{cl%YbU1ah!CbL`!H33}dzr
+htU}qX&Y?3r~m~9(#^Nq?X+Q|_9G!Gbecu}-EupvSfdppnjX=t2xj3q;=s^aZd#RHI3bE@&IndzQQe?
+i1`zO-;MmiOM@SbDofi_1tz~EAd#Gh=@ohyX!HEeD{|0MK8X+k#$FHr^$7_}l_w`+ZnbT?no-_f_d2^
+gF__@%r^IIH%GBQ!NI72pmqqVd1vE@1Pr*4|@_{B@EF0PV~*Do$#zj*ila-FfeFvbZm^^FGfkZ#J7flbgK~uVgNF>Y#FaF`LaUF7%-+Dl7KV>@&S?9zU2OZ+>URZm
+08I^H`XRZB-mZDJ|^~e)wBFx(RzKvAh|7nx7WpE4{G`@lqRnzbUOQa+zItGP;bDTa~9p6_;}JQPNqll
+{?c=cqexYp>wOMvQqd?a(Phwky}+65WS0HZFSa?+{nDh^+sm;N5$kqW|%M-jMb-&VrJWYFY;ULN#F04
+%D&c_;;kb)4@Ign6Q{aT8=KTs))4rLN4~GJJ9cF{|Jba5iQjiDJrX0$TIOnOF^e8sbtn^X)T$NFj-8@
+{iD(+L$w!^1W||6QX|+Kfkl2FcySN}PQI%LV?h@~meaT}{!YWRZ`NhSX?_PZK;&t-(w{Ko2ub;lU!un
+ZJX>5qe<=~GOsoIK!+!4%fY?Ln9d#;VG76M;4bMf#m^kaD;@PQA1r)*v2LSj&^GbPMkK6><66k7}t3G
+%k;6qC2p4udo4tT?R?rHN8dg)qrSbuz1WRSnNFs+5(4TFfe%EoKWbTh8VSp>k7KDv@TRHLsjAy~-W$1
+1NTW?#JpWLXRdK6RW#F?EzNxpE#>lp)
+L@KQmY%OvdbHSvR#Q(wVAd@e}J8Z3r!je`je)Ck^oa=V6;$d%XlO!@K+b%*1V2d^Xy2w4ltjxNEf#-3
+%Z{ALUeFZ1U3)_(q;J7d`IZmt%WR2RXZX+EXcUxBd?R0*?FT5;q^X!cf+#1h3DP+kJ#Eet+Auqb=xQF
+Q9DDq=$BF)ebs7G3HsErkC)iV2`(78&*QQLjTPOCZkd?wy2ag;d-6k?}x1rJg}=7ORhL$$#ZPN^$zNj
+Tg>9AVKS|J*h^19BgTg-Si7jbyU#zk3OeHjX#w6z)PBwmsR4&u*u~na&oyv#6o7)N(dOcowdnwS-4$gvNc5Z?Za7Vbu|?4jtqNxFtz=&^dnjT11v;4IL1ID{P8VIaktI_HeD
+ph^a6s9Mm}XTh}^EIel%nssby5GkriNRV6AM)!D*Xygb1)d3!pB5HAL~sf^1LseG+ybyersu{iS!=M*
+kuF}3F4jbb^91EN5$b*Ak}P;HI_0()yqv%I|AL85vcWASBqD&-~0$E7x=R_5}LkN*5*=wa8h^Qz7UIU
+fvi%EVSL^kBCir+nM7d*!60Km<7sPqu|i+!T_ZXPIzO$7*Xs4&En>KIlwV0X?HObwzqXrbaTfl$l{}e1zjN>Bi_ss8)0_r(d{QhFWq$@a3>XM?j}$soOk6IBI
+&u#!le>AE~2k_cxYGN!FNPI=l!F}8{4xeyA%X@jh$HDXDyZ`<-I6shZfA>e12}YZck^uA^W39p#_)p2
+?1tXA~?YD!vm?bE50NhSCj^BGD}h;>RuQ2#i7i&_fqLqRTWi}nLKk*4+C{cI`FT+ETMNbO%(&2ZV})a
+A$64|l(d)5`Or`KCEg)Hd?>D=q(YqtM3teKWMm{m^@+;W!hw$?$yfP(7znro3iP!k!L^00BCW;vx+q
+NX+TzEJ+U|VJoPE$|UH24jzygsZkM7^8isYA9!ZY7WyC6|sYS73jlUte`zO3Aa%^$)d*#Z|nEMSSVQD
+}>o;@grJ5^0LOK=tx&yb%(S`XAOR9&=~f75q}ZKF-<~5a7#-pFZMQ;&TtGsIz)hE2gRavlZHtiLkZvY
+{ZwSV!k~w`La8T+vQ`*YDPlDZwC{UD=Zn-1a&Y^0ko|)NIqvml?~u{qy;lD}oo_g+kf^F0msufq*K--n%A)TfOG-cds<|N9Wt{C3jrdGQS^DtTw`*s)69PYV+`VKuz?ioy
+k*jhrh$Xa2Iy{pQ=Fyl3ea31xSnpRs%F+~5Fr=M|@FUGJj7EPZA#cIXKycEe0A9=P<3{}8VzrnB+QX`x)Je2|u-Qb7SFaxVx`#8w*J1
+?3mE?gP}g#VDb`lB4rk&&9(}DF1N9{N5@jA)J!i*ogX3G6MTP6My3*$(1bVg6Qn
+!M<@5_Si=GPt~X7Kslz*{=cXBHm-h3x${9v=mfe)LP!=9;k=?-iw;ej0gk3T6$LlFQ7_SU{0H>s^A5X
+RmB11KbUVj#A&NVnbCr5iDb9=9evaaM%=xCes3hP=|4Q^Ujqt`09VfB&Qhe3F5AVAI-HESy7v%BPx6i
+L$CGVeKUA;`6J$?54s&BwuR;+;#PRW5^x1{c7xm^-ibAT-SjIplI?u)PTHiPyg{W)cffQhkmQ6|=OrXGd}tA1a}6Ww-9cMttV?p{AAlI_b6x~JiL+-AL7nso0=65n;wTcxPwCEg;}^VA%A;aqSj&R;3?n
+$c0ZvFlx;`;?_X60p-s5Yv@=La!0Vlg7lsaAl+m^~pwj*M_>Vn$>pu@K3X0Djh9+SIsLaDjd@l=_sol
+#DA(LX;(&l94T@u&$%E?2W2XHVCECWWn9$hLc>O!&WNLBrg8u29`Jo7%R#nbcs~q;;hx;bqPP3bll?F3jeP|ga5#Al)jQ3x_KVI8q`TLdzllbIEagHuxU>s~JyP!>yR
+(CO7kVK@`0wzoO$a#7#)7?Z2S^$Vg-rb~G}mtJs_M#JrkmB3!{49w2^?@W>4O@Xw-K*(-ps(;%|1m^9fopm0d{)eQjvRPV
+r~Jt6SOhBZZ{nvUJ`3Uh3yHs$nK=`gHg#{t|7t9X)aIbjC#7x3p*|N?nxw$cSpgj43U}16A`8*Z>wH@
+*JIFIqUwr8gv07It?g!0&E*`rRiiI@qfLgyEvU*n3S<0rR{Oc{i2pONRc`?tYpf4{_vWiNOMZXYsa^e0Yj~!AIh~K}y6eSo2|#uEhcCW-@|c1Fd2C0wmF
+F~CKcT-qpqrVVbJF0DTl(C`SoE2_aTcP#tr!~U0bTI+ZeW_@dBeEnnCt@+J2Z)Znf|DN9VPual~~t1!
+M7Ri90X)lJFnw6b_0|XQR000O88%p+8N-^Lqfe-)y4>kY*9smFUaA|NaUukZ1WpZv|Y%gPPZf0p`b#h^JX>V>
+WaCyxe{cqbg_ILjkoQk3{=BVp#INS{Q?y|-01xu44>Bk1c&=iY~d66Z7lVj+@>r)(#x6-zXbBX-C4u;?6q0EG38$n6SIy;6Y0g76B>mk4(a3
+Az+XULh7tiTpO>Y*)yXrCcqf05G>~x8f2|UvYz)r4dd%BIH<^2+07sE1_*#v`w|Z}kB{^Hh@FT3LE8#LtQ(<>_cJ>^o;uiL5>%Da%wyb#Pq-!YY
+%>F8_Rbesb~o`tWj4om+=Dx4b%oC&1f-JJv!i>~fx~jpQ+4G=lG&^@=O1BqBEPBo?(_vlr}o1&
+~%ro(_Hyc?uhh5W)a|2P38`IUEdrzBqq-`Y!(I_n<_B4A=^31vJ}T))9{gTeItQ;h4c<
+I{KN7gy60+_>dZfeZk4u;N(>+Vz5c0DZiJ0~ITlzG5oWRnXW(@@Sx!Oo&=7?vK~gt4Xi{Y5*S4^AYK~
+F8M+%#e!D6JG=Pl_-qo~X2ngC=~dTzRq-|ZEK*Kuu1`NqCxH?b*Y9Vagse76HfPg(D`b(A?R#K>v`N7
+8t=>TLx;(v%4Wr(ko=xqt_|x$fEd~3M&T<#@Cp26z1qDiY@o9Q>b$T+5FRo6eS3oUM9cem7<`>d!za#
+ecJDlfy#iIwGj?Yd{;0`oJ
+v79`GqA_1rBFZ!PA1}gjxKvt$7(AEQnl1w&d1YE7$DmB>n=^9_R|c&Tw}
+!J2(Po}*xkJlnHU@+B}XE5NdWABr|e2plrk{(YdSPlbX2z}F!67#PzcAARBse$2-fogfN;l@*2+T3RE
+*(apucRs~@SFbeB8#J*qno}~p>v>CWpB>*8UFqna3py*>G3OE9kQN#it#3h%jq*QEQY}gJW3~T|pqR?
+MyyNd1~UIAhtmL&a0vw0XTQN&fKBb0qC69HSht~&H68MYZ0sWKB)2z(f^H$%fl(9YQN7%>IgkeG;pW`
+>?@)bP_VRO4;7>OH`^S&d_%B5>ua=--9NL;N;kEiX7^KpewYC=wGJBJ?5_Dn1C&9~zyS59l9v2_6jRd
+Z$6?j8KWhm+qMaAOpQ$>>mTsA%lM@LAdBB!{EQca8xfK^tw(w!pF_378?46MkeRG$1t&c!J{3%7`7ZB
+&Unz|dO=v%<>;tQnA!ROdbh~HaDDx)UDdyU&8SOG(%6n^kye9CS!^K
+!DX=<5VSWi-2<<9aEWiRVF+h7HK={K?*i1@EkUp%0Vj3w}1Oc{E;Ds9K$Cszzogp!)z>g{x*t`*wJ~X
+TVyv{z;u@IgqI#*SN)E>F11%XGcnE~3vNrGo!voxs^O+NrbWEBK4v49N32w77?UK}zsQ
+N=Oj;@NTptBVXdGzP>ANMJL_En|!d<2tJ)e>BHbtH?QdDg4rSbG0ccEY*;QgZdqqX$=uELywNT3G?QS
+i4v{IKlXh3K_Bd`B6{BuI8XwS8dX5KHOCz>wZd$gK_Y<2fWF^91lIL;;1NuSwAw$clQM(|3^{BI-Qlp
+a(|^+ZLf%J~^}t#C)nCvcJZX?`c>99=#1{$1v>hfYykwP37I#REE}G!+EpVb%5Gs$n6Jnict8pjrP$7
+fiXT(}r_NxOAb7HmmjWYGK!+O_43lXj19v<|SFn}8Du|w}4zVgs@kwSjV(}oRC(vBf_-d
+Gcgg)FJZF2L-tQd4c~#az0_pvfehRM2LC4Z5TQZVU;BuA$|WAvucW+m8doIVA?JDQmGdJwf1cRm41n?
+4_oz_6JRpXUM#w=%-yTg;a94D{;HxsscYJ3Ms06(_S!iyA)z#DXX^LJ01h<@0{y24y;z`(guDf`b7O0
+5r)VUG)7X%eansLNUgbn#A?|ixug#Ja5(#J-lVEjwzE@+6ko{Q^g=@f8x}$cU#o_1wr$|*6=`%Y5B=X%BL*$3H4hLr%2n>|3BEEAGA3F8a!tYyGJ90%X5}*^eSVmxdN8Ka+(NEfy7Caq3Q>G0)GuYLUBor4ta(uy>;)d7?$Uy-KM#l@>EZ*?@
+80tMA#jAP4hVasWjaQI_q)0GWH`0(`;rz<^v{s7mZj+!t&%N|Yp>wu7rkzye!E1tBUakx(ql&N5*3@HZ7X%?BZeQdI7!pa;z7?k5PRs+W904N?}q4
+^&+9oE8L+@MirpRSMBc@KvyHQ%ClE*VHu)jt>sPpsxc>S2_VupjPfk?9^WLfEo)g`Ex7~gZ)l;jDYVfg&0+xP>fjh~cfPx}ui2
+!V|~l2Bj6Yi;ZsDLEzkM{7V@?!j3=&Kq^J<|mC8YX{#}YAfv&2p%VN!#NW9vIv9EQs{)NU@sL^i%YGX
+Ex}F+8AvuHuokiAU!dYpzUZ2JlF)Xh;5#`d4fRQ|uM{isvQOc~MVL2vGv4%ZOh$c~b}}mbP!eC1Jh!q
+9cEJ$Uc-dX+=nx$dF8u>c@^x`d#)Lq4HwnYfAG5z@aGV>XD;A^@Db&7Hfi&XiZ=kTQ8ET04MC}8Vl?3
+?Vpt-)#f3r&|wxD`U78^LybY@;S9hZI$JfP%a*;52{eegj(QI$k>k*SduZh@rzJuoH;8UYzSPC0ldP-
+Ky1q$-8HqYGG6gO{of65g?7Q(B$Q>ta27WH(+kBH{^IjjTb+g;)yS@`
+8-PR-d2YSvd-H`fs4LNWcA>cAw_n}|}2(dxU2QX|45ya9f|B*qXZhvS#*lOFSw+=?QoYwVaG^a~_%Z#
+%X*2=B6VP*__f3I!xciq)xUQXJ;qKdReH8%~z0^zG}e$?xpysoar#E5s#h$m`+
+P6e&<56#J*L}p#SdN@ihRQdowu!Qn2^J>!bVs5Fz`KLDhwxt;5Izn@tj&%C{KLELN^TamrU={I
+Lw&ZHENEq2bJCNJ_LE5f@ZPMoCkt)4N#BRobzO=@iE7FQ0z2&bR5K^kNTEZW7RGbAU-Rm*p%$ly+t{@
+>DqZ5~yS=o)R?!WaCE%-NCE&@XVhi|P@%oZUgQ{B$`(3ghq5oSIS#w(yIZICp9be&4p=_&bx8iw|F6a
+xkWNS@RjhZ_KLd^kZ)6JTDkc$}F-I&l^^kN2V6sPU{aa0TPr$s^ZPRx@Cida3g012Oc8@`3e{hYkEu6
+2^SYOydTtrL5b*pIn@xBP_T-#xuV&4p{u*{Z5aHGcj27E-9q4GvqqF85{fx)kMW$jvugz5e0Hzy9SpN
+pRl;Sbt6obg3erECVw+q3Wx!`xbQ%Y_UqfmqGAW&egg5A^uD>mwNKx9`1O${#3UAd^Y{<$FFA-9X_GI
+hfmsk{{_ich#&FQsCgj-NF$bHQorYpJzOXm*d0
+t7_|AR#Qxl#sEn6yc?c)%r;4M&kZ$;+##_^UyeYQOVKoz?G^zz@+XAOu>$w0--q)ZA`c_^Ky;>dU8OJ
+*6%^^XJ9f3wJW%YLlWhw^jbBuv?V-=&IW~e7z-HWu5@>`mn`nGrA%(uGcOs5gsR`O<%f3@Rj%A@yH&K
+#1V+TK?s04awJ6%eUsf+3-BSOpX#c>PWf2~nc$G7cF8*w_4l&|o0+C&{{@>P@;4$ezZQX;H%R*$?Bn9
+ii(>YiXtF(RSuQ`*YX5;1E)4(VDgZ`}Nmf$68C_55%~|O3!$p{#Cqg_n50fd7x@*iJ?6d=1b!@#H6Xw
+QSE!Lm^%|OZM$j)BPz-&QhfobO%{yAHPn790H6MTdUiHu?~83cxUEZTfUv1_;tWSEH#tFaCmMR4CR^F
+AABxYpQSK{D^*EPRAJB5G8%|vZzLF+c4iu<|``%jI&fMzEowll%;yl_@8|Q;Q+?5_)?H>9fmQLmTOOy9$#nIu@8J)v<)An2fsb}{-z?A{6XeEfwYd
+5AjLOg&%`=%G0>w0|7{of8Kc2HO;MG7e;yU`bQIp4X94oX$eCiid?UwFJ+iX1%To^AESHYjSxQYEN3g
+vB)#o4OG16{4P_?l}}+%zal+H)SdoxnW9QUHMc9#|wT9hnc<{O|H2NIyhy}s??lRr1wIq_JWVh;*1@x
+o^bu2TW}UnK+$BXADpHmfc4VHZ|ZC@KWSC&tvd@>>6%Lx)m5~{q2jU?)&01MgQD%h31WM-kaw6rN^IR
+{d%xrI2WH+5-b8)>+?y!R!+bRJ@tq-_b|vTK(Q0<|X8sjnuzQ`we
+0SV0XE9Q~UTGQ2!d+;pu>ObMP43+h!2c{|^KXUzfqXJ|d=Z**K2u|7my#@7n_}-^hnyF!&!(O9KQH00
+00802@m7R>_yM1-u&o0J&TM02=@R0B~t=FJEbHbY*gGVQepDcw=R7bZKvHb1ras-8|cJ<2IJ>{tAS>T
+P-CLdoq_)%DYaD)2&Xs(igAoPA27eX^Mm-#uTX`NV_#ERc-Az%+~(K{=@!BzGTk@0Kto7b&{7^SJIM5
+;Nalk;M@Vv^Cll6xhj)9Q=(Eb7UiPItN9|YO0f!~yKpZ3qob=uqQo+ft5k|N?=P>!+jm!@EY_mTMY3G
+wMJZ-Qz7%1$E*D8Q7Y_-3irZDP@`EHRWs!yHEi^yMSF#98=?j7h|H%(48I?G4E~Zk03#TxW0r@OUQ!z
+_YsSMn5A&*ow)d)hHcm&TXH4+LiPh*kgrHJK9X0gbr`O-h~Jn!g8V;kk!ESayuLdn8;R>}`$noP};G>
+^hm*1zU+n49^z3d@Dlwy^EgS{)JU2~4}p^HdocMT;=WMq&;WUQO2{=(Cbx$&JhP3JIrMUj1-B37)^2J
+pcCW?KdOwB8ke8I4hBKc`*{N69vED(Yl4{NW9PB%Mk-2lHbG3^TIFWUn{9f<-^*^8jlNpJc2K31uHLm
+dM44r2a2dXHESZC^Usm_!s9Chlf|+CU{zY~0JRK@yJ}@1Nfm{wU8CY6SUSLWShQJ_Ajd}n{;2mNRJaP
+Jeeeo?*KeCdeT@qerbqSr27CHTS%z^~hNk`3p^`6v(nT_@aHhNZ0hRH8c%VvTRo(rJe;5HQFuf!C%jE
+PdnvMMt80E(BlyF
+2@K*y&2I9%^=5?HR$Q5{K3!&Nq)c}^#j5i;W6H@#EBvTW)VznM6ZVY4*!TLPK2Tdz{;}oO5*}6fy@}d
+AOkf#a*5K@f^i&){9YEbG=Dhpl6jUF(i@7;`r6w;XWoz5h9|W3diqntq5o5!WCKLdSF1EB2fz>cLoiL
+l?C#{MsN@h=J4!>P#Cfus6h&SPtVGt+2f1~%kYWmJj*KTHw(?1u>8>x55JpvH5?LVTOAX^R5|o)aicQoBh0jzZfsH~EgqRYcm{wV|5cv#wIf+KtLuMqn0d`rg%@7O^+T~TjC=
+j@%R0@Q*^2H>&4AYlY*
+QLO;84`b9zay+e)6!`1bH!EeBR!^Qb3_qh?3R^QNgP`U`CDYl+{`$23dIt=8ES_q)ckK~Az^&dxSY&{
+Uck>iBj(KdmIU^etbB%i*+M=|m8*1Vh>45v1_U%pGL@$N|somCTjLQYvYEKyS75(>|D@`fY5E)F0R*Ji9wya
+u?s(Fw7GZyy}BMroek$8BS_mI1C+d+b0=QIDPAW{o}E>CQf|!jw!D;jJa=duPwA6A9kQ%)8
+z&oGqAcX?(M>Ft*pvZjQyZ^R#9vc;&Sqc@Atq1to-@m>;SrNrF5}*GNswO}Drc^
+_uhA<}l6E8p~Rja};4lg$BZYwkX!`+t1(aWjOc2hFf)Z2y`$|LO(xu3^s|w3MFMKe6tlctS3ozn0rQZ^sK4P-FYHw3;)9
+0KME&Xge|Jl?=N<_jNvKU7H}g)ZV$Gb~TqkW$+JJ1|lR9emx6UuPXkv8Vo!0JeWv_&(nhZCG(8d;Ihy
+p5zH@~WM`ISw>-`T-G?*pHQB%$3uf!-pu=fYR^URB>!{!f`Y0$?ftlzmXYw8A2NO6+NBE?di
+I#Iymx?5aX&~7pm^;0tMs$*MP0EU4Y*tN!-TGbU5Dk$fV>h<&$8Ptr>F)LTknDGSIx?}|I;7O(sE#?@U
+@K!$&b;Tq=-N$*;Gx)Xq;H_X54SD0HWnuOYTcdZk!Nk9yhyJQx#~VllekSyj1T)iW?h*}!INv3A~%BL
+%NWyU(rHsDi!r>q7Ppq-;QFlnCd^@T2DRnu**E|osZ=FxP{B?CgMh$Vz!2dyzXyYhXqBw|7FlWonF!-
+uN-54K=i*PFe<^053J_*2%Mj)a>SQIOWR^t4wZ7-zH*~fe5$(5SU}rHtj$w7L-;7}~k{+r&`v>CQ9Rx
+#uL%G>y0^b?Twfk?jBzf)do~S8W|))#iXSmx7Yx`DQ5CnhFX8FiynLBgSxmFJy
+(Q%(!+6NTKH-Qq(m5pEo2~^3Cm0EqJFYN*RC3%E7DuPemf;%XGI7Q2WI=LFXDG-6Rjp{^A(6uvCJdHI
+JMH1Dy_qgWBAm#6&z|!f)8lv*7U5FEKW!2=pAt*A+qOnR8IIpDjNG96R1y;qJIJ`fqyr2T1C@3$#L(|
+WmW(1q6(kjYv_n=7UKF@;e!HZGDejE;JQ44aJrFwbgA*$@5wWR^!b8J=L>5uv-_mTGZbnh>M*BGeN<9PzqK{?t(Tz^;>mfK7^HPXwt_amwUCps*Veh05
+k~o+=gVo5(M)zu>v2S;a)v7nDs&_~PA*|qDjd>Eg~&@p=&+cp>*Mj~H^cTENXZexvJ@MJwfH%qEwow~RWySpFo}
+WR#3PJ>r_g47WLVe(s8?V!2;z~5dl|+h`n!NN)I+o|vWL_b1yht%D`}M@-5RODskz^nyPoDJK9@WKIFw6_^=tg+~XI%y1F_?pt>2)=%W-U^$3<~?x;rIdt7GWq
+@7IVMK?dCrC6gaJ$W=uIOmFKrIq7G$M^~CYl<3j*B&`NC1ihzk
+&DWM{(7=9oqa8h(#*XP$F}SU8HZ1091Ud~&_RT<}3>-D}YQZ@??i=3BsDVvqppN5V=Qk4dpt{%aBh``
+8dBd839e?lpg_J4EN>s@Eq@-?-drh)0jXfFy=Y0mV!NL~FVE2J@+A_!nBp10ukQzvT?!(S3ifUC3U4S
+*c8t$R7bF_14ja6qUIrOF5Q9Vw}y5i_28+jjN+gUwJ(-TiO7SQ%7kAmJ+X@YNh*ex=$_6>a}?xYQ>v)
+V-|_fZTbYFQg`wphb)aZ1-tY!xsRZEe*71PB7F>J=gC;6Bn?Y^dELm|dLH?xPwc0_B#L7zqxpPCiArr
+e;Es*=@Lw<*H8afu7+HT`
++V8tWGA%NK1}?&n<_m%gZ|Jl23PIy5{A{@(`W1bCBwMzV%oo;V-dI$(6su^k%%(Pbyfs*=YkLvqtCs;uGBhrn_9B603ta6!sx!g4
+_7szMNSbPB#XIkbT_S0T+M{W}L&j-oPFoxR?&Nf-%m`3~sKU-Tat&-F)k38e#1LHK6Qj-9X}kk@)eN>
+Vw_!TB8m36omA~5W5tB3C{~Dvl|mk?BR+bruM;lWs{|-K(|*E(U?_fy6%l?A_R~}@*-WJnM|#
+!%cp&eey^|o)KC;2Q3FxH?E3bi+%x(`0bkxn9z!|Kg**xEN^xABLW^~_7W4LQWv;?GX-Uj}g$J;;FEw
+IZh>SY#onKhSTnLHz_^?*Z#RM6bOrBOw}hDQUI;Vw`{tae%
+7cDS#x?szW2C2M_7!Rdkz-RTdzT)K}*>GPC097}Y88^Ik(D3EB+2hGuWDaBouKV;*1o|Egpkcx3Vj_y
+O$TCifr;uO7!QYhwQYUfl65MICx(>18;*(`}*hkzF|bLJG`S%9?|r1=9zizP!Lb^sAqSJi`$Bvtmbg!
+yA|3sj{naYzdaL;?n^vsIFzgwT^w?4B(+qZmtx<;Wynl#cp;_x#er;4@I501|u+w23A74k(9waf&tcc
+L2cXZQi}am7xQn#Pm{@&`kP;RZNDvyqd7c}hVh3kOdXMVzU-2d<-
+ZoeT?bP|Z;Z$HVxmW54%#mnN!5&5?SjshO!YMnpm8ftBJ%*g1q2(<<2-d(f8=uV#RxY*H~IERBebSOq
+5YUl#ZMOyRE8}f-SIDai)b-c?w$-7Bes}(P_8?qyEUHU)QL^AG#XbnG0pP2E*;grjd-1h@{{CnDGym%
+?((cp&noH?s_yrBCWl$lm{+5BMt=4VKw{5Xe|i^|Fdd2m4(_%TpPp$7(-jyh5-|`1h$qy*0l^twKr=H&Hs+T?
+wa{=?Ap^It?$j4d8Nd)7}|XtH+cy2?@XqSDj^>LVT!NS6mbLaa>6npEz#)GSkUyjAm1hKs{NmBvG0oZ)mw1m
+gXT+AuOUrau07DIJN+05yoJi#Id%#O)Y~W>u!z5FFJ`@D$Q}%u8{X6^1|hcw2&_bXEW?<<1G5HVuxNX
+al(u+8u8gdlAj3xC25FuL{;V()MjgdbIjS=+MSI(iX_Al{GnwbE9#r+i+NsxsT5%2T@`IT)isS?SDN%
+jLJwg3t7J;1SDHi^eIJ@NfKAv?r9a`RuX?F^$0RB3j0l|R{5r@|c_LoqF%T(A3b3NV%Hw0xb)vE7q;*
+xI$F0{%AG3aW`rW&Wv#Uw)>h$tet!vC0oHTQTj)rL;i#g+@2Oe`nvc11}-IZKmL>n6l+v-SHij7;)l$
+4pN?dg^kfLp#G3oOhU0fBDe%oF1?=wqb1sEI`qtY?(3661#Vj@3N1Q=`%0<0zB&+TrWhCa&H9F{x!rU4BGzp0o``lE`x
+eDu%WSlR#-j-PJXJ*r99AcIuT@+D-AHQBvnD^c^9nhgAkK(h?^p=&7M_=cM5-4a@f#^bmzU8#RTr#n=
+wwd?ni$1VNj;>{y!6Uw@IIS>29{$-wQrmKr%zT#pK`KpIuDeOx|8yx{LPm?
+DfPKT6faAR;!pCE0;!QX-5J)2i*1C$OTlJ1HO2N;_o5B>IeBDT%xz&Letf*n6@h>&0)IO0zU;o9g)=(rgIRh)$k
+FH0e4d}IPvzIU_(5|d*_;f+UDv-FEguoYL08lXNQ>67llC=b;Knq%kt}=-V49tSNN+C-H(gzDZ>9P#f
+v@6UFGKk^e+9H2r?r<7-OnGnUWFvL@-inoG3c>ieTr6!FN!3NXyqSDJXsHIR=s%w=#9Iw|sk$c*&{^(
+*XTDjU1mnlqbQxybc|h1`a_FWI4Awg=s(N&tsOXIDHGVm4x7lKXLO_BJoSmaoYb8*~q1)@XfSSL
+C9L&$$$x97Ru#E1Vk#GD*;XRE4W^&I;qyk-cnf%7e(*$z0BOfA7CTMyK#>Bll)DpQiX|-D`i9<*+dYp
+E0KzRQ%F>In#xH^x1sHHkhtVJv7p5TqK6O^Ng;x>;jlAhWKY5ZmCV(bL$j6bgOUlg)R=3yNY2uR4wsE0S0YmU&^78ck>#Jb$m#fLe+tb&te+b^3z6JC1MKHOzcy~c#d)|*nXld(
+R;U3api({kK%4cyyWs%D;gJDqBaufi=F{pac`eW8Rf+k5T+)o!*iF0j?q8q9mAc0r%*@zeE65~5y?aJ
+(^tdVoi1JhlH7>EGvZ|qD=NI3ZGqh0!{LA|Rd7VS4YkxAWS@5xIfQdAWMLT-$r98wtSJW|PdFdQJQGE
+7c|nLy54Jlglg7hm265T6Fo9CBayeVC)cSca3pdZlQ4Nug9}<>WVYRkZb%GY!i*JDhw
+qn@VsPLsVwW7?~n$~2J_Fwg}v=u(&AowVL06(GaDqu_5(wOHSN1){&ZY#uw(`3evWJurR%OweA${Cxq
+f2%QnuYExV-QFmzjPl4I)P<#+7dfXmpT~tQ6msTN%+s9)9WKtw1xxfFc`L1KPus1*Q*e!niSz&rF;*?
+9x=4k&0^_mBI%>zDVa3|Mbiz1(!+enG?-LUTK=fRuFO-h3=5()R4L6)QA(h_BHvNm1AJXA
+thX3Sph7I=>y|v%+N8J!YUf^#dpV!Q!GB_+f8jChorOvjW09=p9E7;Af3h%{m8JakF99{c==q8nY>QVBbaL{JY7-WY!y7{r>DMrxVH;o?v^U;=}dn7^1ue!Cw`id>;)dfn_&N1
+Rrbv^VJ>iXnm=RU8AQ*QoeG9X&}lj@jR)z$(POUvpX^wM0)sIMvZ&*9U(x|Ln-ea4C7gtIx$7xC5BIa
+`Qy46_)JX|&2&;GHz1>>Zxe08noa7$hjvjQ#KvuPSHbFI$z^roQMz_r2~>+}=%x74&rqSRQwfVCya_S
+$g%=&_4Qp;MA$Xw{qj)6Rmyt
+?aZnLX>M;y{%&&dHH_8_S^*jX9^=us7sD`#notfkewh~o-KXXIb3=XK=)mJzW|Wa+v;DL34CW&X|HOR
+hxba9#=B)n%P)h>@6aWAK2ml*O_EskVqo?Qq002}0000#L003}la4%nJZggdGZeeUMZDDC{E^v80kil
+xhFbsz8ehQJ(3Y~o!Yzu71oyKN+NJv4cNQhtasYJ1-tE{6v-IUeB+zDmX&JiY^EYaP^Rn&xLBg_?`@_o1&v~&MZO9KQH0000802@m7R%wcA+pq}$0Qnm
+L02%-Q0B~t=FJEbHbY*gGVQepOd2n)XYGq?|E^v9(S>JEmI1YZFze49eWDLwWEpWwU2gn|Fv0Yq%-d?
+aRun$2n(Ae^f8js|Yg1*xoa|=)k!d??jo!0bcb
+%x6)XD})rubeoZs#C!61=PYaejq^n8ggaHpK@wpa#wu||U!Oxwtj&~i$Pw$M;W)WV~68DFDqd&p+e;#BP
+7qnjF-_ly}@RCS5o6Q>0vbD794x+Kv?bG
+-^W>{^V!vQuuw*TU6CRmV43y%+d;TQW{h{~Xt$Xfv^Fd)`MYFbgFtu1W;A`!)Ijp=Xa0z~A9%);lYV}
+D=bya7M%M7L<+uWWQ-8MkCqJUT~F|*>S6`921}WPS(7i>dXtZi4w!A(7E;g_pL;zO20A|5&edYohloUBtnz4m@)G_SIbd_^6FGP=48kKIBTWd(HaQ6@
+h!w1pS2%XBZ)QRo(s0Fd60Cbz}l+T~3FEeJApl{wb;9usbT6R!|>?YxDK&6{~yjBCu5^z`7Brsa1J|s;#$GPvRHo}Tuq=i`EHcq=EE-5{;J1#TQjawKi
+b;qBvLSy0ODpQIlo4REm%^*T1!+x^_#$AS-kBkN*oXY(F1uL=!3xV?OBQ^qPUSGbRQ^l;x*}}1gTIJ30km&aEqXdG5!`+avbZOL%c*Q4B(}Rny_M{nbm
+lJX=ei?D1U(^y~5)4d;`D$Pd9W{$~^}qYEc3l2Y{?*6R-hbbr>PsOWX&62%$Y5ir5b
+Y=<8E+P2BNDTCHBbE>xfJM<=JoXtM@wwM(y*y&y}yQlbF_X9;~Jp!_08M2KTPGBR~S~;Sq6T4
+#N;x>%f7(or^Aagb=683RIpIy#k9Gi{A-$YT!~T{v*0H@PV5vgqQNK(DfFcD%b4Vz8gDh$be{13$hd_
+ZcNf4WD|nyzHgsF452+F}31DFJUZ&I_5xesppKmOtH^L@Yhf#Ti(*U%7e8OW+Ou!iqdq
+1Bf)f+WEi_yT1>JEqpxFhxhbOu4=U}DA@-bg>w2C_d*W#Y4_2fs9eW0*Vwh`^iF8}KSY?
+1vp#n1Fj+E$-|a3#h2v^F#)Wsv4kkD>4v?XsVi~cMeou4~f-|z}*t>4V*7kuMJVda`>^fq*Sa|48aus
+0N6Oi|bxJ1mD(#B)#CC=rU(>HJS=AmES#=&lcVIo;XFJQ&dp%61pS9w$q&VX?n?%0CVEz6nD=q)G~9iUqHC+5HSnW*Zu7B(VtS=KjoI-;hqLO{M^66t_3+&
+Y43_b(3q~d7?cvNq_Hq=E@j-pu3>+-1)oX_vbN8;@Dy!A@>~zM&;)$ACScN75kDr{mpzWa&x7J5AxlI
+JHmXPMCpVbVXsW(>E`sjQgU~Lcd>za(+s_$yZr|^{72Az81pkaai$~RB~aAJ2C1}%pF;{hfL~J=JFpk
+7T%apygF4&bD)uvydZNx(lx6{xzz*TERCPDN4E-HF>SZS(b+Jix()Y({l@B`ax_HG7h7>`v4tERz3Ly
+?zI^!TOEdHsX$tOl)?*Jd-EnG6(r##^!8ijvg)#19GraHvAW?CNWi1s$#=hk#NLNPjuBtS9%DdPIVyN
+!b6D(px(iTxt122^?vv0BD)#<%Y@o{c9!0mqr(29(=Cp!i01(jCyvc=XxTrPCR0LZ%@`>ft$-60j*O9
+xEH*jxYiX(F9J5M(`-dPLXajP!F*O=JPCUMRzjV$gM=06aA%DyW`jot=!9wn-EZ#Nz;7-KkvwCB6~Ot
+$(ANSNI-rlr}LVs^3SQpsN|QtJ&0!+5Z4gO9KQH0000802@m7R(R!
+C9=Qqt0Oub703-ka0B~t=FJEbHbY*gGVQepRWo%|&Z*_EJVRU6=Ut?%xV{0yOd8JufkK4Eve)q3n9Sk
+BbJVFsH+QO)}n`S1vL9Kc!;2)!nk2jRgDsIfhv$B?*w?brEKfDpV$
+Wy7z87tkX(>zP7j~)Im15&xKPp-I=TdIBqT2dbS?fA&REJ8cxK8!XzpA5}6^-1}m1$O{N^!+RM}=LoC
+-kufUcdRgIxg9Zv@Fw2$(QUxWO~Wo)>_CaEoT;@7ImE5^D37OF3s*zA}qe=jlwC9$WX^pWgil^dy1eD
+*)GmxrDVxH>`#qs>qn06)8?+N{YkE}wC0Z-)%-#GgV%T-k}oRF8?Y3MD(4R%pe#9b(fISPMRoVWFFp=
+u+2=_?uO09^yZX{BP66+^PDu?OV9Rxq^CE3ao$%^jG_u7oP~q=HePvgsfIu2<&g33e>LEV7dUNsi!`0)*A%f;uOR
+RQ8Ml*&F$!o)v2Ee_{Qo~Cjn?&z~N`Ptk4X2KJC#&Ub@%Z(HC^;NS^S!@4%|E6Ys7my{PH20{vK`Ov6
+7s!79xC1}S;OlRryj?<7b@!vxKeGylUy{jS#t6G`Z9U>%QaiG`PskE&-UkM`Ssb)tFvELXIKB3&t@}<
+0?@6sLxh^m=#C#(45qSR=ReTz%E$?;$>yjLA=dN^v1c%pXkj)
+94(l#5#cG#6d-i?Qfh^)3f5^oaVs-!x*KJc7I8b5&4VhN)f&_I%^w?VsjS!8{NfN;%3&=8sG)Wdck#A
+(>&eMmE#g?g+iA^^+x1^wT3mQLto;ql_C;i|AVViY_p0MlY$h3qo)12uYXZ!(d!a
+Xh;#~)$4Q2;=n4%d>+34t-8`EpWY<^o825nu`5;3TV)OAZ2*$eLHt6pc$}lnhqa^Hvw>qvb0V3c6ih&yT3oO;DHNdA(R_*0V7{6UW
+oVN7NijMtddllXXkbNrXoThw(bH9X1N0{a#S?br)2(+$libc#%`;Jm%xTq@#0Sc;75msKJ;EX(9dk7l
+iSzf=GAUAG1F%YVA=&679yx`ns-q$%D_c%_b{`a7>ESAmF*E-}waR#bUO;1AcFBZjdOszP;I?j=QzH-4Y$Rb#FZ>E{_=I!%CPs@@V8yv=&p}l-M@^w04G2?7
+g4aa}ql)C^>b)vM^x*=9J#2!i+CgY(4=G67$ef}SkuO(~i+_-$xR
+*%regnyK^lV1Q_1(5_u{Khc)>?dVso)X4SEQK@5CL}Aal6S1GE{{hiB$8oozbA{qNcVC}
+)pN>+m@$&zMx)XS{8VP0$+BsF`i0eHe*b8W;Oj+jX9irb)S1Yg3$zzB)D8_Ow!q7>C#(oehA;h-zFiN
+3dCM|W>lj^?%d?Kga%mQ*q^voV{>X3iEZ`R^#0LU{6!9MkHK=j)d#Vio23u9H*kmZk~Lc&P0Icd0{Qp
+*&NulWjQ8T2D^0dY(@2Uyk_TH&GI*Xy~G<9IoMJa5(t;{5C7
++B2|q|3!24e{*Mj}nxJsFzG+8$_|;5%%|yu=}uzEE*g&|6X)(e_D?KgY7|x1Ig1wop2sFXM{6ricX<6
+msCKmrz~tXlA#MAHwVt
+|VrBv+DOMquK&xDsVoq355*}1u0(~56Bi*JYiX=vVec9m#jSbLu|9W-#K6&x{#m|??#jE$!k^_@0>}*owmPfiVcY@*Cb|w;_31Q(I86iV?vLddrl&<(o^~Z)DO;Ca>E|u^@kYZxK|Lc$4=U-&lygX%3
+Kd>7zoFU@l$=x7d}~8sh|6B%`ua=%N5E?K7{+TFDwy5{USfY?0-;80|XQR000O88%p+8=?eQ`WDEcRLM{LR9smFUaA|NaUukZ1WpZv|Y%h0cWo2w
+%Vs&Y3WMy(LaCzMtYmeNv@%#P?Mn+Hx(W!BeN4vlVXdJsRki>!Qq#ulfz>~PU%4sDq;v||uW}
+7IAHk<0ywf(^A_HY0o^V-U+IFHt=gQ(z5+f)Uwt6xOk@DpJ4hVDc!0rMq%dWj{ixdYJhM>U!<{lg`xYJhy8
+HY+&D(*XVhMfeOymX?b5g&-znun~d1sA{38|Dlm%hx0!iyy^P35YlnNy3VKJbx^R-4rw-kZ{huG5LE&3Xi?A#Z0sk8$4HY8Z
+4P5g0C$C52|qU*6tn2r&cv(?T;m%Wi*t$>d7f-Gxi}mE$G**b0c(4a744}5iN<{tzvEZGe0=rSJiGoV
+xtiWS_~;4-q7+xZ{QGyWUO#{Kw>QrL*;gpw*Fui<0Jtcgy~8N?Zf+jeD*i3aTyHj;QtVk?w9UZ5&@7F
+y+%j4TA^b^Vb4|aH<-C(R?@p+wdPQMLVn8_ExgGx)qE!%;3s41ibBd&rb{xznvh47KG
+hPk+J95A%k?$UVV~1HJj1j9xSJCh>F;iA=n#-R1?c2F>J_VJA!AC^~Daq%_$Gq1|vx=+n)d-`
+8q)!z{2h$HvsY>_(7lqoV;|T;;pW@KNoN8c=$J$q|;QJ@9z~q(FvHfdN<9oCwn81LI&ZC((=g%qDPY!
+L@{5@LCFYt4eC_>`It$n6|_CC?n2D`Q8Kx;>~TTFu}y>#G8_}O?}oF;)$j
+m5u0Qax$QuF%69AvikREW{NZAgM_dl?1#PJ<;rr*`v(G>OnAvRPnnt(*?XS|h%)2wh$)6zR*=!Xjo6S
+r%>RTvuJE;Djx>r@x&&_
+3j8!Gy3oR47Au>~!hxIxjL@%mT?}pf?s`Wd7*lMxrvpUt{K-}y+ADbEl6&pp0pnF(JrV;DjLxzS+%cuR4WRiJ^^qx>A
+?1NoEf@Av}jC}Es?$=-u%~2E|8}YsaO9M;7;}|1^%#Qu~LH}FIA|Lty9)uH72Esq_A@=D30V-0b5>2s
+%2YZmh@<~4qIs&d#(tzU$9W_e^G!ghe#u3vSS(c=pj2&2CqVA%5i|cBBr1s5
+9|nhi|hz>2~;haQ{tGA4w9;T#u)i3ykC|(;NR#Qm-8U5bay|862h!1ODjp@mhp&MlG
+cji{dhsmNpVCwo-IbH7};>W~ysqwB661{za$9YBWv@QOiu#+FW3O7eQl8A
+DI%CBdUcZo69
+!oaWK0-OaVVynb#wyJeGF21j!)cgCpcxG`Vo3&uZuJtz^y#Q&*0=CW9ogSv2=%i#eT(EU~3oe3qa^F|
+f`Y|DjKj}2Z1i!z_a*ZB9t$1X^Iv*n%>@BGX;Tl}D0b(a_)Kb@+2G&o;OlGaD-OGjl7suXfC>M=k+wN
+(?lWx#~m2cD(zi4N@Mr3`ZOfl%9P%T+>1SM&`*c53h3b?HRp9OUA42|m%YUT{1vs@9K6^jllY?jRycI
+J@pXEMa^8$!DMMW(A*lE_>!Aq~&=fi%YQ70X>TMWpx0C(q2vPHJ5iuF2sqVJN}7ltPmp|HlZC^U~L-s=>+`O|>?Ox~P_Ba%bs4X8`@W1YEPXPVD
+zC5yg6$BIrzGb&yEK(SKx>-*jX1k0_p)sil_d6_Qt!Ryv<{9}iz)e!oK0I0*1XN9lF28}?4>a05}nzX
+u|f@7h_5qw3L#3}jVEN>s
+pE%7pitq%!p|H;ix3dF&-r~_5uTg=Gg$O5U_ecn;jS2D~I3`$T`i^pr3!
+#1a)_Jv-X+$Aez~r-DN(dtp9;3?V*})`6CM3r!u#r9bV^7H~gx=m0>;SzS)uvIiF|KC+xK-+FsdV+1@
+vG%S#5wy8~!QUsC(mtPWzN$iUD=-+IY7?KcBBjJ)T~;9Qi60sa&mlA$_N-V5uoD@YeyR^9UgB7t1=?R
+Yqx5Qhc&hiveJZ*EP7n6z29`*AC7AgBwWu&C!Yj*x-=yu7krPT|_?OSnEd;4_F6K
+urzQyrZ@PrUTUIou=^~x&g=N-jam%Fnf@MUn3sx?iK*|Ff@-ko^-5sHC-Dh2j6R0W7k(o8*N8M5K1|~
+i^OXy?DRTb7T96!dk)gihyPPC8{{&D=0|XQR000O88%p+8&UflGXaE2Jga7~l9RL6TaA|NaUukZ1WpZ
+v|Y%gPMX)j-2X>MtBUtcb8c_oZ74g(Pjj$fq)2mk;S8UO$z0001RX>c!JX>N37a&BR4FJo+JFJX0bZ)0z5aBO9CX>V>WaCx0r
+O_SR;620qJVClnO1LE8=FVBjt^q)0dVv2S4$yxh%|8(o1sR;=Z(ASP3{zUhoDM!B}3^JU@Z9!W
+=^OJ4i+~D}tF>VR1UqW;dM6{V#UBf{mRLH#(=_#5CPeO}&Bx)5VVMX;hqm`{VO{@$3En9zNWCzDr0WG(-jXHg{IpL*qv>-O0xMt)7JhFxSDlO@c~
+_Kkq;O_9@xss;#+Bv(1~;c8~x8FgcwF{fbh_HZv;$nqkkXlM-MBLA&L~>bWIvtXg)iSgXnVX2;HVQVK
+aIiZP5ME^yW}F_YT82gK318iF#7BE_z9&VUu0?ZUZZkQ^y08hT=@KD0MV%PmM^JeZZ}e`6D%ZfzV)03
+Z3O;AF%6%ub*hMWQ8-VD6X6IlUP^8pevaGQlteu2aymiqo1@RDoGHM^?8tp;ib$kc*UG*J%=G
+bVKDqWr2;a@cxHvC8C3hP_id3It2`B$aT+Ie!FnOcRz31PS0-mrQZ;Ljkfy|rias&g*yTlheaEdwu~
+J36Z1BkbQ<8ha_Qn{&{7yJmgh0ji_uh^%XXjTbSxhFYA7#f5_@7FR*lMWu4I)Kk~0HTS&j}_mQXhYip
+uh56#k_wm-rjR;U@vq;SU-K|3W?)X^UgtkOctrB87}J#B>n`0Lm@Nk6FY{M%i}E3AR)6(yOt`0-Vfhv
+UUp~1xDA5O-^#Nz8S6&MV^nmIzmzX|Bjm*=%8j;1@*nzk|AsaXL70UsJ7F1679
+G&h(c#vQ@!dGl}M@=$5*2=S?pu$N%vzQ;7c|;o~Q4FkY{}O8UKy*hkh+-$p`Ez+C5X{Co!kInr@NGr}
+nwhwqEYJO;ycTi=!2!Odm^J0VH_7j)I><~-a&;VyWU%yN6?`oFNAFrEC%Z0s5~^L$}CJBQ5*`&@Gg!=
+!)Z*t}~l+l6@zh`Pc4S>WH`P^d?(C&k{fQsCjVERjqtmjadAAGpeLDz<74lNCfHJ@w~PSk6IXhT4PP0
+s5&YD!B=bvK;z%N6Mj+@6ln}`B;ZV;xmX&f)P2fI0QV9Gt$fwYA63l$(&%5?L(sh1CyZvhr5NMnqK@9%7cd5CJ?YzG#8x5ER;
+6(pkDP36$Eh9LjUV9=`$kBpWg;&=#C{qHw0J4QJ7ydlHTl-3V3cp~uN5%Fhnc
+NZY=uBN?w2f;c6Hxfvi7|z{*mIWUPefqL`OFow_o-1Dh*}=HCZ5OX?XAbeGJycO^v
+L>G!Cka?49;AJMF}?Kl}olPPSJP3jtu~-F>d!UDu~Drrs<6>^LgpVFH5uDJzWWvSUtdBW$a)X({&^>Jj8
+GVk-oy^5!7ot
+994L{BFdDM35$XLG2bU>A?bD5WrW~3Jx*wXsIZa`j#(GB0itA4x{^N$m?lZg1wCceE(eS0M;hoW3;YN
+nzV^X|uR__Mjg8DCTC0FR_Yr?T_ye436pya;+P)h>@6aWAK2ml*O_Evldu6ol7000&u001EX003}la4
+%nJZggdGZeeUMV{B1i_`Sl3nTDPNXr55@GF>(N7~2kxTs5MXpk5gCH1<
+)GpH|XX_%3a;+0vEhKugQR$}l8b&%@t9n&uc_xgN^?QBX9_pXD5s_Sp=*wtr^sYX`%I!}gDWvs#`5^<
+5YUd(7Oxcx460u6;l-(+uPuX3TE1imD%9cfz$dNyosVwBLGL5wfjf%FcPn!wLXoEN0v`(rtmj+0~C{f
+kXXcuco%boy)E}V)vbdt-rygFEF{>rcZtlsA7DGW{#ra4Hu^#b0zohf_DVgX}quH{-3Np63XN%ldToy
+dXa6k3~$ys+RambOAZ9x*&O4-a<_Q}^@!;pWr)?&*@yZ5DK0^NL$MbneVmv0?nl|
+>h0Yd`KokbFAJ5#*UGfEo>Fy;Z%`6MN@BycN+diwm%G|Kwley{Gm&q(-lxN+@Mp`&sSaKm{Y9pad5j>
+u^;@1kvobF-Ud7!>d^=^M3fV^HJTrPTWhwbYqW&u#Pp3~qgp9|k_PHhAp&KYqo5S*u$p^VXoS6d!Ou1
+PteLL1oqE{l}w2V~_8;cEjbG2Z86XUBgc_9*+zE(I*k-};lUZk-!yxAX(IEN)UXEXL3lpYnlj7FqM1l
+O$2UC`F8TN`tpik;+qG8&Csf~-7oJP^E-bLTUKg$(BmYy%}SC#@;?s*w=qKLvD13F!5lt&m0m@j
+1DZ8YsMj=$~hlfnXF+i(09mS_W~r%p?p(TyDZ(sGht_cV+);1&kH@a$RNoitPxK}%^qlkL;~mOPj{mv
+h`sVxce_kVvw?gxt|4oU%-k9GbK2P@znrauUn(CRPkv>;5Hr69UBGpf&SqatcH>F+n22M(1pxP1dgk=
+h1tDrP`lq{$tQZ;PPdV;6k9EX(7pgMEtoqSx_ch&j>+`xwJ@-QB1!^TTHVEETu4kFu>LQ6rFjiI`mTQ
+)8l`Y@d2BkZzk7Cg+@Y2#yc5V1D5}C?fMj*v|er(X?8zqjkXK}Ww%Atq+NO;FBcbImmP1*unfZ=>Ak&
+TcDMN%!en4$n~FB9BANM^_+&
+4De9njjym42i*5BJ8S0nE^^%=Vb#lfG!9cdo9Te@Si}YKhqhrJ}c?h
+*c>0>Afb2cXBY;V!AQ2314#T~rq*slAsfGWg&x`c9TPy38EIvjH`JgciF{5hZiPi`n{jz^%7RpLhcak
+s9+`hwXJ~8925;0EJ|nzdoDxw<8XX#;&}uGpKHs5R+<_74+l>aj^AAp#N-9H0U9R-s!}~UgcX)htO1A>Uh$H>hr`MP0vTQzg
+9?PNe_KhRqeJwGqX?IvBJNH@ptUAWV^zmfkq)yE@%0yx02AJbbeN*SXQ?Z8mnec#>I~oE&`?mK&8(tVEp)*kh#@kZW``OU3xjQhNcQ4jxlGAeF<(mzaIlr}8IMmeHtF?)9XD%gnDZ}k_p`I?kZ+L_;FARv0+iGAea>tqBehm=JF$lM_fmi@
+R%M9|DNT`=<}yj9@YP5Zbc?ZWFz*qwHM(gF3sOwk-02jdZKQ~{KE-7aG$$l``5xSsLk=>b4^cou1zmp
+7Oj914vi+8BI#5F$EWe-M^M|L!V!rsu=L`P$bb0@9cXjjl2$f@8(kThuLiH+)a=8I66t+E~a-T|HY0Q
+Gt*aMed!LYVrNode#&~js{7s&~ysg+o^x~?D~A!Lb&V4GGcpkAagi)-?Oo+q^5Ft>;DP32>EmC^B%<=
+yq&Ia?xiBRyhHA@ndQwQ*zws%p&NOSyFE=>5HQ-ak}lsp7*Y;jnZN*cxTf+g2yBM;;y>cby$(7Y+YgB
+YWF=@?a~vxvIgb~%(73HQPjXO91MFdY>FAVi
+deUA>V>NW$d5)ui51_Hl2Berfb#ubB;Fqj-+tsQmfqHS1p5%P$ni-KB~tdBeIV7Q-~;=uB
+1#s-@1AevMc9IF7rsUQ`Wl4oE+8ZgxaYS$XxBI1qsPn<(rHOmn0~S`4+v1^^_fSQPqeO*r?hN?0d`3e
+q`%JY{G%^Nzaj$j=_%IZ^&u7R~6LRyK6eS+_i`lKjFq)zC~R#BbYdSQ8=%!@1MgkoYLu+ru(2t;CR35
+Q2(euOoV$QGCQtNcLgBUz(v*9ae}4mu|NCi<03sA&mGlsG&og{zea}|Dw-bMvFhk4r&?v*&2k#au|Mv
+F8a`5uT@LOD!r!3}T!_07;Vl~GxePu@faBn~9KO%!)5i-sE;Ls-I?PUaG?uGkGY)R5D}3$kNt)kFoKs&+PDwbN`c8bw=s$y>D24$0-&Fd~*FKT7pUEFu_g
+__X`!mvaNfG>^BI4Aips!SKAR5Ngh^On3%3?j485#N+W2t{l+bOVaA|NaUukZ1WpZv|Y%
+gPMX)j}KWN&bEX>V?GE^v9>J!^B@Mv~w8D`w%kBDqUqD2nY`$5u&7_EI_(79@uvA`svLp
+k%Jg|NZ(kFA$VvDz3XaS6LE)nVy;Mo_2)@7llIxBAAecD(
++_gS-4=?p&{_0#oQ7xTWFr3L)2s;vU3jqXi#bFX!w)@fDiI#%m!Eq|*UUFa&!ExhJFo57rmMpp%nKce
+N;%W{*?i+G@$^^PWF
+e!oOUF(1ybTQyR@r1?hI49$#epw0ChkRtE4Yeba1IWB-YCxGvCngcWa0IN5E(|n{ZH^72D?g#{rlRMy
+zvWnN~>^8+!6DI7EfM?1go27a7Pn{I$D(GtXA)e(~OpC2dzmu|9WHj^MTBlW2cJ|
+22SsHZs`Qr3EIXOOgb(;L}`g{bO+az^0OV$-^t@m9ZR^?phb-XC6RoW!Lq)k=k2HcPI^n@Qi&^s&U`n
+~CNwmp0OK-(s*a8~PWvxF@oeJ<(Y89nRjOH_!LX}#2TtrwT)@7}&lUL9Y&I=$H6X_eJ8(*@YSDW|}sy
+&iHk5a`ITcMRjdp=X1HNSh?XUatotNmXf9>xB1AqLCH@Chw%hqTFO+xp`IJ(3Tsj@ptNNS?Ebm!9U#C
+`1>5;g6NQbByEs&X$ZBUzj>gTM;*pJ>Udn;)C@`~GZO&gPez304E8>^Gjp@;c3RMEQE2e^xX0pPzR&>
+hwTM12tdHDqqCUWjkA4%gJ>vz)wn3z#_%{$r19%zN>pW`)!{M;!CNHal*9POO^wuJrw8nsbPH6vL0Y*
+o-v9CvV^}8F4aPEnd7V0&~#lj(jny7O6o1Qh)fW}j5JXRpfH!F~@4N37iOn~)CADN_2lD{vqysvKcw)
+R#={R=1jWbgYk@mC3`^QI$AHsZNlML1q`&4`2TYPbd@y)=+nMAwElLg;Zip9@k4@=M52F*f|hLWtQ?&
+u)`B=&hvEtUO%zdLlx3B<*->PZaJjSPLnaUEco@6$Q>57ctkcSyx#>A{)lr)D795w2gWgqAo*zX`Gy4wf}>h9O+G~4qAL|v}YMj*}L+Fa9LL&n}TE7EG4ltsQpLiu8T0LvH*N9sc~0gO
++gkIW-AEqAWbc+z0H={gaD0DwWSJ&Tq;`NL5#ByJi+xt;S(QC2#D?$7p_dX2IQl(-m%z&GW6k(cSb7C
+DKtQywvDwE|Y6LBM2!v9&;|%S|=Y)V|8VwzSqMJ{i|*J{%inV(HNXXA*k_~
+`EqRd5_W-shJORgjS%(Yaf`*;fOJONYCJB=F)U9=0a${S3k2kXF>}Nbv6*etf@P3Jz_^2>EQ3@70O<{
+ZBxymJ6exg(v3o3>l`TSF?S4cH{>iw@9QjEGtdG58glo`U(Zf2teTDa(n^`
+YHr>4ZZgxqq8?mN_w@(1q*%V{(*mISmasoytmZ(I+<7x=qiq5=mzL8^PM*t#m$=?%sl%1u*ebBaBHg$j08y_tfo1Y%eFGLZJ7PUosz80Uh*6)2bLW)8-#$e|(OC96
+x=_ImyV)EE!}b^^G+1A@*s1;s=%GMf!_h0sTe6gc@V#a&jF1;r@t(kjEHuzp3s;A3eBc7yTc_IStdQr&0_I6g*z!EVBh?$o4VH?@wPXQC
+mFnneZJS#Uv(_+Z4{8hZn?m%7v6;Ob!H%)8<0I)IV^Q?jQ9KpnpDA-Ww>ZlD-=7nmqm9AxNMF|FdjxP
+TpI0plcdIxJVAP?PY(ZyyB_y>(oU@+p$i?r0Jvjdsrmw*~|*5a4&*(cbYl%ez>RO9BBIfA@FZ}fl}{=
+?0XfCTgNpFEaVJi$d=L)PX2T;oYGW*{|~l@*X_T^4iFho$=4@%h`=Z(oko>G}D)a|U&C{`&Iu$?+SbH
++?v{z5|bJ7)Uc$D>951i)%FykP}cOL*^a8(<9ey4Pv`==o|ST#)~Z1{u3~-y+(`{_j#q86g2#
+in9d%Cya&AGvlQiLi8_x(nBqei`y){0r=m{1fw==6%Ujs}=zQWEnVHJ7X@x#J_|qE>O9O&PhDbEe-{Nj-WV0nO*bL0^tR}-5UF0jXijlI)LQ1is>_K$&L5hg9XO>ZCg^p2<@skuKi@Cu5F6cw9PIe{mI1oWF&^^>V;&)LA!1%~FuwP;L;
+Z!qrY7lvKko6z6n~^fviQil@}3#V%NoIhYe}**5fQ+GLVnH5PCFJ3d&83qgL(+o{A@W=gKS7qXZY)Wn
+62^mbO^oFTj8xT|WhbHG;KgB_jOrC8fC*l5(V)1St=n1K%t#?!Gvuoy7JXvK7c
+p^VsH{Og4hO#vd*xNsEUHlzufUudoR7Z-2Ti3j!VRN^*93roR5_h1Dt~fN5%er{lBNLyE8Sn
+GM(n6(}cslcyPZj|PBfK>b4%Y>?=CLi9{+UuC?WAvTHSI|A`XQ3#@`c3@5?GW9_Oiyl)lr5%5rnFR9WL=Z(ahP
+5lPGRPUF!Y3fhW}JYQX2o?Wz2<{edk2F7OLc5y&oX*U@n%0G@r7y-M
+H?vwl;KfJbrX;9Lvjmr9HlcNhX#6AaaRfsna$`0cE5xxxOB+)~0DyPMkKk)B`E)9weHO*(BN{6U25Oy
+`Vym&g2P|p3iLngPblnZ9Yfc~@J^WLI~caY{;;?ym@y_iC?AhJ5{02nqY7~topkhQ8RN^zd7)$x$|sORp@cS(xrRfSyu@zKg>8
+|ZtyY`Llz{KIxe<0De)!d_NZ3Y8s~$?CgD%S!i_Lj2UoRuA8b(0Sz`Wq+BWF5_5npVP^lbsBA}D^d3y
+x;ezDATwLxkn`u!~5z*?MuAqY|CiQVy?N+TD4!qwM)S(W$sTg`)8ldbDK9Q
+>we+6Cz}+j%S$nQ!7bfloVKO?ett>>yz+PWm*i(JmGsMSbmw0FU3gk@k^ispbZc4<@U&%GkEb8czr-~rAQ(i6ur-<-vb(
+}$Z7!JG%bgyWPcy2`vOn-JP`Zcbfd=MIJU?vR=@9G&q}!qYcNl;|?%@=5FXfz({H+=0yI9E?iDeBBH!
+jzh!?V?stq;t__u2S!;J2i}Xjr|`X
+f83#q;h0aWX-tI%*H2!r-2h9Z~j?}0r!Qq
+Nl}E!4fHjuI1`;SmN<4Cu-P{U<Gj1q>yZY+dzK~(qzl7O84=V%A%CD#abQJ6M>iOjQnkU*+Ti3_u2
+du3_E*St1(K?2-74R=Qildc@0gOVQy&oz+*n>h+AwK48B}K~FjW@b7_%j_t4i%yX)_rT=eE|dC@a-!a
+kmDY7u<*EIvz`UVHW!dJ7MU4$GacN5)&gO$`4rs3LdL87)iBM7}IIF7DQAttb06
+kWZhS#YW$m{QZNqyj=0!T_|f@7twD!0#x~uD6d3al?Mv5bX0gUiayz7&Yx9MyaG@Ml@qG(F9AUQWZSt?r`|WehPtn0gJ_2ph8;5ld)Thn%9a|moBUhddXL!01~$4hF!8k%3;j_;i-SCWYjM$<@(N+jwFZ99GZ2xbs@W;^?~*6!R->F4eP;dMV0Thu_
+>s&zGYFEHh!7Bbz@8zqO(zGDLl|JM8ZQYktX=wyAB~(S-S$C{Tc5P4PH}t*TzxM!ZEX5rC11^#pl4bz
+A50E?x|(R71%~$wR(sK+o-E>J+wT7^?*KEo6Jg)`0;H=e3<8_>GIxpJM7L6bS$>4~|Q(Q%Y_rAPeE96PhQ`6)R5T-dwjc>rfq1|^
+G9u#1c@-+}|9V@fzoSTgMe(u^z3F+T_#Z4jF?M!l#3%)p=m35o^U(ZIx1ufRnH?i&f9#!g8LMnACNLB
+u#fmEnCkST2jYZHg(40;Q`Abi_fDv)5~mi*+k
+vI71}4LBUNKsm&ri=vE%Kv9Z=+D&HfwBZs7fd$q-*Ck=uDb;$+0bqH9h8pGh)hXKrIp*4Xp3_t-ih
+vKIgWg+)2z&;Q*8J*9F)*u=U+5s8DHvroY+Um?Q_KYV8p6hZCY9nl`@;O%fic|FKE(_I`9rU5r!KmVq
++)V9F}uadQ1L&N`Fiw)*6=6M`$vIWc8W3gS3Wz`;XpLsR%%qjCw0Cn1V9>^U-FEH_pGBd{8;bxmruyp
+-Gx7mjH(eG2Lq=>{g0oNd51|j<`dW-=lVeu$BmR5KwC5v2?RT~AK?^%^on95e-0dkN{MJ4UobS}XH5<
+*A8IFaJ3NVslC$WDPQ3{$T0{+EjGoTW)SSPMIYPYiE|^~SVdcE>ga1kPqP?1%pj4f8+4Xdji}8fLxE>
+ZJ)$3&zoevv=TmsiGL6kq9=Oj)`y-!1$K$yW(3)pl{X)3mB6~{M;ioh#Xuh6-2P^+z+`1U=PBpXk#jH
+K*>E2Q0Cyv)MnTrhHZWePgNGtYcn;qF6){ic7lZV7K~awO+i-1^A3TH1j>I>ZDQjXj-m@65F?qn$Z
+=TWG(W2rW`i#yOMJJ)B@($H=~NazGxdmRbQ;3SY8L{)Px_wSDKceWuc@i+DEvoa>u(wgr*6Oz$
+#)GIZw9=Pf!E8jo7{BP?&%_d(2r_a*OWVwNI_c#!p%IB~tzn{df
+(Mm@d{Nz2BDwa?1bHR>&~AmKZ{RXfb;h?ypg*6AUqT7b>ZdyjG$0j8cX5!Z_LJ@_;~m;t~5-MjCFKksf3BGZ>hYC)%RNi$((~ncxeG;$
+f03b;}*%LdJzO0E_;@Xi{5f_7LHUSA^hiLD`
+8sEO{=$~<$|mheL*fjDztiCk02`KFm&I^Nz$$tpM$`BNc(r3%5J#k$gUO}+`;VsKw{1yEMHy3q-Kt-%
++{k$cJ6HR5lB1I00|G`}e)^9;Neya$R!pj1KQGd^(0&8;zX`vMC$D(s>=@Fh0@AyJBV+{z>keGdNSh8
+<%tcaEJq0anF70!tkrlP>?v|ahO0}dk!g5`v3JjTvP^)mg-*
+Z($YT}7QvXLAkwZj=6m#?IWcW+mgcq}0yf%PtD_1pbSy(AL@*h6BX>=}9crqcD!#%<-
+Z!no*(4)PKE<(qk9%aJ}n7UWjLu2_ep>d;N0D9ZBnk3;k|@0FnKGdZC3lbE)eCwcX=!ppGC}xEchHH0R(c{{h3JbVx`b!Tr(AS@Ajcr^@&mrsKmk
+c-OF9U%xid5;V}-c;!ee#HvAs!)afwqG$f_*3%+0v6m!EVcZ-Acg;@wWnJzm=h8O;v16|9u#?3IzL^d
+?0laClQNROz)&*%mP6r?I*WK8;vM^<$*gHA
+28$3<+gik$J(Fy*9wKs-}s`Y2CDaNZ)eck6uU!m!=
+!=xUhP<`VO5uRGeUdQCZ!F+~C$$Hc0n_t+rRdR^0a^kb(!DU+hctPXkf7C=GKWsbDC+sj4=HA!N`>M`CnMU-v$|cUq!Dc-?W%?e%%*
+32(m#?uuW7XS;&H8n^a<=Hxuvsm*|Ty{8UZz-4X6t@Qn%X@$T0Vcqfx2Biv`X-#B;>=Ec&r-0ovudc{
+2EQeyX{eZg;VCi;HsPm*q2br0muQTHIY*3)a}-6Z9n;+=%vCwGye_b^NulKIn956%0c8DS^9x;=Q(tp
+Hq;mYfS;?CvAW*dNVxijQQl@V3H7u$jHJ&qsW4onMRG@YFqV_qcg8{vS|F0|XQR000O88%p+8B1cUvD
+**ri1_J;9Bme*aaA|NaUukZ1WpZv|Y%gPMX)j}MZEaz0WM5-%ZggdMbS`jtg;K#zgfI}j_bZw`L4th1
+CTmQL8V`Drc-d^43d3$=X~{6G@b|Wh6hY$7p`kNx9`C)hL|gIP8iJ0r`rxXnEfRm|6uptmKa+7-Nh6P
+dtn>@i*UB76@cn>z84wA70s|Hy>NL^c0(j7mxoT^8u8(5$gkPwHXyCCltsf_Y|?@
+bazAbT%1;nR7TlsoI-!ZeoFm9ncwe2f7^*iYQPz7(BS+|5D0dIh=&-BKB;^@8jFC_@_H`Z$XtJSS13J
+*xt6yLdi(ksU6fiR@1{S8_q17R`#sbS`8mA!grDO9KQH0000802@m7R%ULjxDo{b000XB02u%P0B~t=
+FJEbHbY*gGVQepBY-ulTVQFqIaCv=JZExc?4F2w4p}83542g^T8XyA>cQ>p+(K~cE>_br)icQ;E70Hm
+~q}j0lzK4>XG`-#ew9pGlkq;k|&hz|>Q#;5ksk0<9sG+RzE;*c)wN|AZgRSju&=g&plwX|E_cV7>nQ
+D;ElbhAzu7cuDn&wTtbmeD-+K~+Zt3l5-#TZUHU)1o)IqTk%9)r>+!G;D$3GyssIN|7K#nJuC|}#r$&MPaJ+hlW`Di8J
+kDM>X*PaAFyo`yD=<{n^>g8TpCu|Edp@-RaGXpzg7hTEF2B0)Y(~J!id
+#MB9=B3E3;LhcgUs$-&>iho({d*iyLBFuS6ZGONManxGE5qw(XwW)WOO$avWjhotx(hDgh;b&>^?K_(
+Ej7LvW`Lm2$}`kE6r90yq{drjurM;EVwwX3>OHFSM7GQ=y$tvJ)GBj98wIwyA}41Mh-qP6w7<4~jq47
+lAxl~F=weWFcZIp5n+H{xL3lV}01A?aeC030XSO>)wasG$Wb?p#c~N=)F@D=tLfezOMxl1p(Q@nq~FS
+*yu$x~B&Y3U9$F{69z&Q>e_|J`p1f8)uA5anlOK3t-^~pfL$&S#vSKHnC%KKk=Z-EHX&Nic_Vv^?*F1JlyRk3
+wd`6AlCvw>2&Vl**J^sExhGwEfE4l`H864+`5m*qr)=r7a|44W>+d(WkqDo}83@Kpn7&2csvITfu=#S
+0^>yi9IPgk}9LaXOXvBQZdO<2F+hu+AE46CD|c^{LxGaPxog!96|#u{MAy&UYIjl3rNVO3-c91XAA
+^+m(<^=b{yY*uhFR#S!Ad_P#A}mi`g$+`GP+m6Lc_d9r5!)3P1!r7}loDo6`SYa>?q?PiG%4~U#=4!<
+kqM%ReSQxr0dXc^WQ-xD
+JbVC+zNs8swE5c=}l4DGo3&&X8|1wdHnI{EA@!#sfy~_meJH&bQwR<*XDxgsO(Hqi=DVUh@>vs+bOLK
+UD8;Z88N!(+izz3L!syIwGky~mCz@6aWA
+K2ml*O_Etc+9(rX4008n3001BW003}la4%nJZggdGZeeUMV{BqHObI+ZkDEgIXE)
+EqVO+|A?N~uULg*K|q4G0Jo1z}H(L@i?O7^x{2q@hYPHH)HXHsf_8l_665C*AHFC3B{Sm7|`OO4h_28
+?@t=KXYvo@})6c3R)%Prfn)V>ognQq!|~6DG;UmmRCi_#Dm{@U?x+o5OiO$40NY9*)PR{uv$C!se7bkSy=Mi_;of)!&1+s!m3h&W8TOR
+3&iBDPtNlqSIeBnvc%{>t>pTn>=wuH}X|U8C4oN!9W?4q7D$B^4Y@=K;YFLIT&boj^35o2JcQM+{X0w
+8o#2D47KKBtvtgShmGUB8+3crk1dw1(3UmE6-RI0$`N`*3^xMR2RM;M&62=1({9j
+};kJ2OTfM+ovvh5QUR;dcAjeD;v=R6HL*Hc+jI1*6^|#_7CNd*7Tma7-R<}+a?Z#&P*proF60KF^r?#
+{*>M&Ev-iw>mjP-UdTrs8nPHzlN;4C+yz9zR)imfRLooI5a-K?RKOlcCWx4V>0EK^hgA;VEwG~<`JQZ
+t>_*R+t<{e|zmR8V55ix{RJZV=jxSM&1e@A~go*^b9o2Kn4XZI@xMw#?@5mQ`XO#XUxrl>x7p$%(Jh^
+sd#tSA4FOOMsGN((_EZg@xV9MeU-r@y9C^q~yT#>L6Q~zPxJd^+ZlLTaDY
+#;}k?NQbsv#iE3m&*KEuac`m@6r2@C<9&8K|VZx6#7m0u|VLX&r@wS>77NdUq%
+Z2d1a1D;V*60r3bMZqbvM$^T{*;hw8(-}Na7NeP{^FwW=o>8Ds3X}+b7wP4Av6+>hH}DZ4Pd(m
+T%@CoXb=scJWGmDrOT_LpU9KDFcoh>-IW$q%X8H*;WM+(g0p
+WM<@a82vAhcOU{ZAv>PdygY2kZ#7JZ-+v+VWEl_Qv$Jb;KIKcKyKhCXki<
+ERx+Rm3t!)qp6v;JwGkU57*4E)2#*|R6FhaBIK#i7~lyD;-uCnhp>giTl}hS%V;RRO6J(_Ui(j;r`-v
+<7XDl!Y*oRq0DT>M;YcMgnv4Xe^L04r>`u)B6B*`8Jvj6oxn|HHNLkT?FTwl{26<9Qrp4SqLHguzqgh
+o#kEJ$+fengRFR>sQwwPw1-vJQYxA3#WuKQsKO-ER{ED*x)`(>k!<8RD
+zir1Vr1%$Y<|MZJ
+7_BMjTtMvgZy_~lK=j`4tiJraoOEe$K7*mW#jNt}WQra|F*yX&Y$2}uQ*^-)382h4Uy93Mw=S~1LB1_
+`tU9h(pK2)ITV$^JdedNGitvLx}A3>|rMR2>?jh3O#YWcuUgWzToznb*=vws0lO9KQH0000802@m7R+
+NCe|H}*j06{7M02=@R0B~t=FJEbHbY*gGVQepBY-ulWVRCb2axQRr&06bk+_(|{?!SVNQCP~!O438WE
+G|LQyc#q}g5c7k*jos+#NFjtE9ykb&erh%-Wfh5QlcFv=!Xh{qX#)0&ht0JO_JnqR5Btu#YIax+Dq0G
+$<)v#Npf<+51r^0X~b^F+nsxa(R!qNmis~RW_&tIcdOV}b=}kMWZR1asYHt}8h%0C(4p&kCZ+kVjvXx
+GzP)IVIeEn^m6NxuWj$4*&&hi(;l-~**RT^atK(f!a-iI|v?;2F7gA9*$g&bOlaAV#5C8b^@n0Xx*Pl
+Os{2ZMq+AZG=unY>q&U)#;%)`gmeJ}bLC6t9M4qR4_OwVdssge$AU*d#v=$o8K)hlr!M00Y2@&&m+bA
+m2*Oug6d(z0ynft6);a&l5NR7&!xXG$Ia&YJFx=nquEvZ>QZ@vf~IFj5Dfv(*WK3pDzIbU2%{{&53xC
+s(Y$5TO4(3@2MR9`8ma7upbTe$BR|gne>VmZ@aTHYbm0zmqGYt4EJ6ugAxXoIfWYM9a+5c%7P6Iw851
+@nMjPTrff#zH3=cuDRNe^b5T<@E{A&V%2kPpd&4_(mFZNuO(6~9o1w5QvCVJVj$OY0peB94sfT(JzWg
+D=C#`EZ^ouk5hY0Ez(KR6I$e$hGt6St
+aLW%QHV&QBYFLYdWJaEorx7~5g*2hkr_KVh*uE6KWU{=X%>Ygl5`i$rjEZm|IJhEQl2#}kGJ>H_4bR+
+Y_rioIH@|4aHG1Kld$xKlj5P)2EvFY9)oidvPGssZ1gII`rkW-2=LD!`*<4ycd?6u^BP%EG%
+^lJ@1s|YSu;rm?G;rxfLjbEeEvhlT?s1=(x;Sl|0YLh)}d5
+vi}z;n`NGC#ax~BuOvD)0JtV(ve%^Rzg!j