mirror of
https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git
synced 2026-08-29 10:09:32 +02:00
254 lines
8.2 KiB
Python
254 lines
8.2 KiB
Python
|
|
import hashlib
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
import re
|
|||
|
|
|
|||
|
|
|
|||
|
|
AUDIO_EXTENSIONS = {
|
|||
|
|
".wav",
|
|||
|
|
".flac",
|
|||
|
|
".mp3",
|
|||
|
|
".m4a",
|
|||
|
|
".ogg",
|
|||
|
|
".opus",
|
|||
|
|
".aac",
|
|||
|
|
".wma",
|
|||
|
|
".mp4",
|
|||
|
|
".mkv",
|
|||
|
|
".webm",
|
|||
|
|
}
|
|||
|
|
SPEAKER_ID_MIN = 0
|
|||
|
|
SPEAKER_ID_MAX = 109
|
|||
|
|
MANIFEST_VERSION = 1
|
|||
|
|
SPEAKER_DIR_RE = re.compile(r"^(.+)_(\d+)_(\d+)$")
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ManifestError(Exception):
|
|||
|
|
def __init__(self, key, *values):
|
|||
|
|
self.key = key
|
|||
|
|
self.values = values
|
|||
|
|
super().__init__(key, *values)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def audio_files(folder):
|
|||
|
|
result = []
|
|||
|
|
if not os.path.isdir(folder):
|
|||
|
|
return result
|
|||
|
|
for root, dirs, files in os.walk(folder):
|
|||
|
|
dirs.sort()
|
|||
|
|
for name in sorted(files):
|
|||
|
|
if os.path.splitext(name)[1].lower() in AUDIO_EXTENSIONS:
|
|||
|
|
result.append(os.path.abspath(os.path.join(root, name)))
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _manifest_entry(path, speaker_name, speaker_id, repeat, index):
|
|||
|
|
digest = hashlib.sha1(
|
|||
|
|
os.path.normcase(os.path.abspath(path)).encode("utf8")
|
|||
|
|
).hexdigest()[:10]
|
|||
|
|
return {
|
|||
|
|
"path": os.path.abspath(path),
|
|||
|
|
"speaker_name": speaker_name,
|
|||
|
|
"speaker_id": int(speaker_id),
|
|||
|
|
"repeat": int(repeat),
|
|||
|
|
"output_key": "ms%04d_s%03d_%s" % (index, int(speaker_id), digest),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_manifest_from_root(root):
|
|||
|
|
root = os.path.abspath(str(root or "").strip())
|
|||
|
|
if not os.path.isdir(root):
|
|||
|
|
raise ManifestError("多说话人训练集总文件夹不存在:%s", root)
|
|||
|
|
entries = []
|
|||
|
|
invalid = []
|
|||
|
|
names_by_id = {}
|
|||
|
|
child_dirs = [
|
|||
|
|
os.path.join(root, name)
|
|||
|
|
for name in sorted(os.listdir(root))
|
|||
|
|
if os.path.isdir(os.path.join(root, name))
|
|||
|
|
]
|
|||
|
|
if not child_dirs:
|
|||
|
|
raise ManifestError("多说话人训练集总文件夹中没有直接子文件夹")
|
|||
|
|
for child in child_dirs:
|
|||
|
|
name = os.path.basename(child)
|
|||
|
|
match = SPEAKER_DIR_RE.match(name)
|
|||
|
|
if not match:
|
|||
|
|
invalid.append(name)
|
|||
|
|
continue
|
|||
|
|
speaker_name = match.group(1).strip()
|
|||
|
|
speaker_id = int(match.group(2))
|
|||
|
|
repeat = int(match.group(3))
|
|||
|
|
files = audio_files(child)
|
|||
|
|
invalid_name = (
|
|||
|
|
not speaker_name
|
|||
|
|
or "|" in speaker_name
|
|||
|
|
or "\n" in speaker_name
|
|||
|
|
or "\r" in speaker_name
|
|||
|
|
)
|
|||
|
|
inconsistent = (
|
|||
|
|
speaker_id in names_by_id and names_by_id[speaker_id] != speaker_name
|
|||
|
|
)
|
|||
|
|
if (
|
|||
|
|
invalid_name
|
|||
|
|
or speaker_id < SPEAKER_ID_MIN
|
|||
|
|
or speaker_id > SPEAKER_ID_MAX
|
|||
|
|
or repeat < 1
|
|||
|
|
or not files
|
|||
|
|
or inconsistent
|
|||
|
|
):
|
|||
|
|
invalid.append(name)
|
|||
|
|
continue
|
|||
|
|
names_by_id[speaker_id] = speaker_name
|
|||
|
|
for path in files:
|
|||
|
|
entries.append(
|
|||
|
|
_manifest_entry(
|
|||
|
|
path, speaker_name, speaker_id, repeat, len(entries)
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
if invalid:
|
|||
|
|
raise ManifestError(
|
|||
|
|
"多说话人子文件夹无效(格式应为名称_ID_重复次数、ID为0~109、重复次数为正整数、同一ID的名称需一致且目录需有音频):%s",
|
|||
|
|
", ".join(invalid),
|
|||
|
|
)
|
|||
|
|
if not entries:
|
|||
|
|
raise ManifestError("多说话人训练集总文件夹中没有有效音频")
|
|||
|
|
return {
|
|||
|
|
"version": MANIFEST_VERSION,
|
|||
|
|
"source": "folder_scan",
|
|||
|
|
"root": root,
|
|||
|
|
"speakers": [
|
|||
|
|
{"id": speaker_id, "name": names_by_id[speaker_id]}
|
|||
|
|
for speaker_id in sorted(names_by_id)
|
|||
|
|
],
|
|||
|
|
"entries": entries,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_manifest_from_rows(rows, root=""):
|
|||
|
|
entries = []
|
|||
|
|
valid_rows = []
|
|||
|
|
invalid_rows = []
|
|||
|
|
names_by_id = {}
|
|||
|
|
for row_index, row in enumerate(rows, 1):
|
|||
|
|
path, speaker_name, speaker_id, repeat = [
|
|||
|
|
"" if value is None else str(value).strip() for value in row
|
|||
|
|
]
|
|||
|
|
if not path and not speaker_name and not speaker_id and not repeat:
|
|||
|
|
continue
|
|||
|
|
try:
|
|||
|
|
speaker_id_int = int(float(speaker_id))
|
|||
|
|
repeat_int = int(float(repeat))
|
|||
|
|
if str(speaker_id_int) != speaker_id and str(float(speaker_id_int)) != speaker_id:
|
|||
|
|
raise ValueError
|
|||
|
|
if str(repeat_int) != repeat and str(float(repeat_int)) != repeat:
|
|||
|
|
raise ValueError
|
|||
|
|
if speaker_id_int < SPEAKER_ID_MIN or speaker_id_int > SPEAKER_ID_MAX or repeat_int < 1:
|
|||
|
|
raise ValueError
|
|||
|
|
except (TypeError, ValueError):
|
|||
|
|
invalid_rows.append(row_index)
|
|||
|
|
continue
|
|||
|
|
invalid_name = (
|
|||
|
|
not speaker_name
|
|||
|
|
or "|" in speaker_name
|
|||
|
|
or "\n" in speaker_name
|
|||
|
|
or "\r" in speaker_name
|
|||
|
|
)
|
|||
|
|
inconsistent = (
|
|||
|
|
speaker_id_int in names_by_id
|
|||
|
|
and names_by_id[speaker_id_int] != speaker_name
|
|||
|
|
)
|
|||
|
|
path = os.path.abspath(path)
|
|||
|
|
files = audio_files(path)
|
|||
|
|
if invalid_name or inconsistent or not files:
|
|||
|
|
invalid_rows.append(row_index)
|
|||
|
|
continue
|
|||
|
|
names_by_id[speaker_id_int] = speaker_name
|
|||
|
|
valid_rows.append(
|
|||
|
|
{
|
|||
|
|
"path": path,
|
|||
|
|
"speaker_name": speaker_name,
|
|||
|
|
"speaker_id": speaker_id_int,
|
|||
|
|
"repeat": repeat_int,
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
for file_path in files:
|
|||
|
|
entries.append(
|
|||
|
|
_manifest_entry(
|
|||
|
|
file_path,
|
|||
|
|
speaker_name,
|
|||
|
|
speaker_id_int,
|
|||
|
|
repeat_int,
|
|||
|
|
len(entries),
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
if not entries:
|
|||
|
|
raise ManifestError("没有有效的多说话人训练集行")
|
|||
|
|
manifest = {
|
|||
|
|
"version": MANIFEST_VERSION,
|
|||
|
|
"source": "helper",
|
|||
|
|
"root": os.path.abspath(root) if root else "",
|
|||
|
|
"rows": valid_rows,
|
|||
|
|
"speakers": [
|
|||
|
|
{"id": speaker_id, "name": names_by_id[speaker_id]}
|
|||
|
|
for speaker_id in sorted(names_by_id)
|
|||
|
|
],
|
|||
|
|
"entries": entries,
|
|||
|
|
}
|
|||
|
|
return manifest, invalid_rows
|
|||
|
|
|
|||
|
|
|
|||
|
|
def write_manifest(exp_dir, manifest):
|
|||
|
|
os.makedirs(exp_dir, exist_ok=True)
|
|||
|
|
path = os.path.join(exp_dir, "multispeaker_manifest.json")
|
|||
|
|
with open(path, "w", encoding="utf8") as file:
|
|||
|
|
json.dump(manifest, file, ensure_ascii=False, indent=2)
|
|||
|
|
file.write("\n")
|
|||
|
|
return path
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load_manifest(exp_dir):
|
|||
|
|
path = os.path.join(exp_dir, "multispeaker_manifest.json")
|
|||
|
|
if not os.path.isfile(path):
|
|||
|
|
raise ManifestError("多说话人训练集清单不存在,请先提交辅助清单或填写总文件夹")
|
|||
|
|
with open(path, "r", encoding="utf8") as file:
|
|||
|
|
manifest = json.load(file)
|
|||
|
|
entries = manifest.get("entries") if isinstance(manifest, dict) else None
|
|||
|
|
if not isinstance(entries, list) or not entries:
|
|||
|
|
raise ManifestError("多说话人训练集清单没有有效音频")
|
|||
|
|
seen = set()
|
|||
|
|
names_by_id = {}
|
|||
|
|
for entry in entries:
|
|||
|
|
try:
|
|||
|
|
path_value = os.path.abspath(str(entry["path"]))
|
|||
|
|
speaker_name = str(entry["speaker_name"]).strip()
|
|||
|
|
speaker_id = int(entry["speaker_id"])
|
|||
|
|
repeat = int(entry["repeat"])
|
|||
|
|
output_key = str(entry["output_key"])
|
|||
|
|
except (KeyError, TypeError, ValueError):
|
|||
|
|
raise ManifestError("多说话人训练集清单格式错误")
|
|||
|
|
inconsistent = (
|
|||
|
|
speaker_id in names_by_id and names_by_id[speaker_id] != speaker_name
|
|||
|
|
)
|
|||
|
|
if (
|
|||
|
|
not os.path.isfile(path_value)
|
|||
|
|
or not speaker_name
|
|||
|
|
or "|" in speaker_name
|
|||
|
|
or "\n" in speaker_name
|
|||
|
|
or "\r" in speaker_name
|
|||
|
|
or speaker_id < 0
|
|||
|
|
or speaker_id > 109
|
|||
|
|
or repeat < 1
|
|||
|
|
or not output_key
|
|||
|
|
or inconsistent
|
|||
|
|
):
|
|||
|
|
raise ManifestError("多说话人训练集清单包含无效条目:%s", path_value)
|
|||
|
|
if output_key in seen:
|
|||
|
|
raise ManifestError("多说话人训练集清单存在重复输出标识:%s", output_key)
|
|||
|
|
seen.add(output_key)
|
|||
|
|
names_by_id[speaker_id] = speaker_name
|
|||
|
|
manifest["speakers"] = [
|
|||
|
|
{"id": speaker_id, "name": names_by_id[speaker_id]}
|
|||
|
|
for speaker_id in sorted(names_by_id)
|
|||
|
|
]
|
|||
|
|
return manifest
|