Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,13 @@ TTS_OUTPUT_LANGUAGE=日文 # 日文 | 英文
# RENDER_FRAME_SOURCE=auto # auto | static
# RENDER_FRAME_BUDGET_MIB=auto # auto | 384 | 768 | 2048
# END GENERATED CONFIG: graphics_frame_source

# BEGIN GENERATED CONFIG: tts_japanese_dictionary
# Japanese pronunciation dictionary
ENABLE_JAPANESE_USER_DICTIONARY=true
# END GENERATED CONFIG: tts_japanese_dictionary

# BEGIN GENERATED CONFIG: tts_japanese_reading
# Experimental Japanese contextual readings
ENABLE_JAPANESE_CONTEXT_READING=false
# END GENERATED CONFIG: tts_japanese_reading
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ assets/companion/**
assets/images/*
!assets/images/amadeus_desktop_wallpaper.png
assets/previews/**
assets/text/ja/**
assets/scenarios/**
assets/audio/sfx/*.wav

Expand Down
78 changes: 13 additions & 65 deletions GPT_SoVITS/text/japanese.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,11 @@
# modified from https://github.com/CjangCjengh/vits/blob/main/text/japanese.py
import re
import os
import hashlib
try:
import pyopenjtalk
current_file_path = os.path.dirname(__file__)

# 防止win下无法读取模型
if os.name == 'nt':
python_dir = os.getcwd()
OPEN_JTALK_DICT_DIR = pyopenjtalk.OPEN_JTALK_DICT_DIR.decode("utf-8")
if not (re.match(r'^[A-Za-z0-9_/\\:.\-]*$', OPEN_JTALK_DICT_DIR)):
if (OPEN_JTALK_DICT_DIR[:len(python_dir)].upper() == python_dir.upper()):
OPEN_JTALK_DICT_DIR = os.path.join(os.path.relpath(OPEN_JTALK_DICT_DIR,python_dir))
else:
import shutil
if not os.path.exists('TEMP'):
os.mkdir('TEMP')
if not os.path.exists(os.path.join("TEMP", "ja")):
os.mkdir(os.path.join("TEMP", "ja"))
if os.path.exists(os.path.join("TEMP", "ja", "open_jtalk_dic")):
shutil.rmtree(os.path.join("TEMP", "ja", "open_jtalk_dic"))
shutil.copytree(pyopenjtalk.OPEN_JTALK_DICT_DIR.decode("utf-8"), os.path.join("TEMP", "ja", "open_jtalk_dic"), )
OPEN_JTALK_DICT_DIR = os.path.join("TEMP", "ja", "open_jtalk_dic")
pyopenjtalk.OPEN_JTALK_DICT_DIR = OPEN_JTALK_DICT_DIR.encode("utf-8")

if not (re.match(r'^[A-Za-z0-9_/\\:.\-]*$', current_file_path)):
if (current_file_path[:len(python_dir)].upper() == python_dir.upper()):
current_file_path = os.path.join(os.path.relpath(current_file_path,python_dir))
else:
if not os.path.exists('TEMP'):
os.mkdir('TEMP')
if not os.path.exists(os.path.join("TEMP", "ja")):
os.mkdir(os.path.join("TEMP", "ja"))
if not os.path.exists(os.path.join("TEMP", "ja", "ja_userdic")):
os.mkdir(os.path.join("TEMP", "ja", "ja_userdic"))
shutil.copyfile(os.path.join(current_file_path, "ja_userdic", "userdict.csv"),os.path.join("TEMP", "ja", "ja_userdic", "userdict.csv"))
current_file_path = os.path.join("TEMP", "ja")


def get_hash(fp: str) -> str:
hash_md5 = hashlib.md5()
with open(fp, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()

USERDIC_CSV_PATH = os.path.join(current_file_path, "ja_userdic", "userdict.csv")
USERDIC_BIN_PATH = os.path.join(current_file_path, "ja_userdic", "user.dict")
USERDIC_HASH_PATH = os.path.join(current_file_path, "ja_userdic", "userdict.md5")
# 如果没有用户词典,就生成一个;如果有,就检查md5,如果不一样,就重新生成
if os.path.exists(USERDIC_CSV_PATH):
if not os.path.exists(USERDIC_BIN_PATH) or get_hash(USERDIC_CSV_PATH) != open(USERDIC_HASH_PATH, "r",encoding='utf-8').read():
pyopenjtalk.mecab_dict_index(USERDIC_CSV_PATH, USERDIC_BIN_PATH)
with open(USERDIC_HASH_PATH, "w", encoding='utf-8') as f:
f.write(get_hash(USERDIC_CSV_PATH))

if os.path.exists(USERDIC_BIN_PATH):
pyopenjtalk.update_global_jtalk_with_user_dict(USERDIC_BIN_PATH)
except Exception as e:
# print(e)
import pyopenjtalk
# failed to load user dictionary, ignore.
pass
import pyopenjtalk

from tts.japanese_dictionary import configure_japanese_dictionary, with_latin_word_boundaries
from tts.japanese_reading import run_frontend

configure_japanese_dictionary(pyopenjtalk)


from text.symbols import punctuation
Expand All @@ -73,6 +16,9 @@ def get_hash(fp: str) -> str:

# Regular expression matching non-Japanese characters or punctuation marks:
_japanese_marks = re.compile(
# Preserve punctuation inside Latin words so a native dictionary can match
# ballet's / coca-cola. Standalone punctuation retains the usual boundary.
r"(?!(?<=[A-Za-zA-Za-z])(?:['’']|[\-−-](?=[A-Za-zA-Za-z])))"
r"[^A-Za-z\d\u3005\u3040-\u30ff\u4e00-\u9fff\uff11-\uff19\uff21-\uff3a\uff41-\uff5a\uff66-\uff9d]"
)

Expand Down Expand Up @@ -148,7 +94,9 @@ def preprocess_jap(text, with_prosody=False):
if with_prosody:
text += pyopenjtalk_g2p_prosody(sentence)[1:-1]
else:
p = pyopenjtalk.g2p(sentence)
features = run_frontend(pyopenjtalk, with_latin_word_boundaries(sentence))
labels = pyopenjtalk.make_label(features)
p = " ".join(re.search(r"\-(.*?)\+", label).group(1) for label in labels[1:-1])
text += p.split(" ")

if i < len(marks):
Expand Down Expand Up @@ -188,7 +136,7 @@ def pyopenjtalk_g2p_prosody(text, drop_unvoiced_vowels=True):
modeling for neural TTS`: https://doi.org/10.1587/transinf.2020EDP7104

"""
labels = pyopenjtalk.make_label(pyopenjtalk.run_frontend(text))
labels = pyopenjtalk.make_label(run_frontend(pyopenjtalk, with_latin_word_boundaries(text)))
N = len(labels)

phones = []
Expand Down
16 changes: 13 additions & 3 deletions LICENSES/GPT-SoVITS-NESTED-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,19 @@ in `LICENSES/Bert-VITS2-Cantonese-Yue-AGPL-3.0.txt`.

### Generated or unverified dictionaries and caches

The public source archive excludes `GPT_SoVITS/text/ja_userdic/**` because no
source or redistribution record accompanies the 38 MiB custom dictionary.
The Japanese frontend already treats the user dictionary as optional.
The public source archive continues to exclude the local
`GPT_SoVITS/text/ja_userdic/**` directory and compiled dictionaries. On
2026-10-16 its CSV was matched, after CRLF-to-LF normalization, to the canonical
GPT-SoVITS CSV at `1b47ca07ef43a284e0b8bd7f29d3e1e3ff76082a`, blob
`2be580b461185ce18c9000d9ec6c8032623e17fd`. This supersedes the earlier
unrecorded-source classification for those verified contents; arbitrary local
copies and edits are not thereby approved for publication.

The optional `japanese-pronunciation` pack is prepared separately from the pinned
source, with explicit corrections, provenance and MIT/CMUdict/Amadeus notices.
See `LICENSES/JAPANESE-DICTIONARY.md` for evidence and source-chain limitations.
`assets/text/ja/**` remains outside source archives. The frontend treats the
pack as optional and keeps compiled files in a disposable local cache.

It also excludes `GPT_SoVITS/text/**/*.pickle`. These are generated lookup
caches: English and G2PW rebuild their caches from tracked text dictionaries,
Expand Down
94 changes: 94 additions & 0 deletions LICENSES/JAPANESE-DICTIONARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Optional Japanese pronunciation dictionary

Checked on 2026-10-16 against the canonical `RVC-Boss/GPT-SoVITS` repository
at commit `1b47ca07ef43a284e0b8bd7f29d3e1e3ff76082a`:

- Source: `GPT_SoVITS/text/ja_userdic/userdict.csv`.
- Git blob: `2be580b461185ce18c9000d9ec6c8032623e17fd`.
- SHA-256 of the UTF-8/LF file:
`d857e443ee48d9641096816a98996669602895411e4330d7d91d1dbe1103389f`.
- Source size: 17,180,971 bytes, 134,201 entries.
- The existing local CSV matches this source after CRLF-to-LF normalization.
This comparison identifies its contents, not the original download event.
- Last upstream CSV change: `eee607b71dae07fb3b29f8f2727490039b3ac984`.

Source permalink:
https://github.com/RVC-Boss/GPT-SoVITS/blob/1b47ca07ef43a284e0b8bd7f29d3e1e3ff76082a/GPT_SoVITS/text/ja_userdic/userdict.csv

## Attribution and license basis

The immediate upstream repository carries the MIT license, copyright 2024
RVC-Boss. Preserve its complete notice as `GPT-SoVITS-MIT.txt`.

Upstream PR https://github.com/RVC-Boss/GPT-SoVITS/pull/1660 describes a
CMUdict-to-Katakana conversion using ChatGPT and DeepSeek. PR
https://github.com/RVC-Boss/GPT-SoVITS/pull/1670 restores that dictionary
after fixing imports. The author explicitly identifies imperfect translation
accuracy. The exact original CMUdict revision and per-entry verification are
not recorded in those PRs. Preserve `CMUdict-BSD-2-Clause.txt` for this stated
source chain; its existing 0.07 notice does not establish the exact translation
input revision. This release relies on the immediate upstream MIT grant,
consistent with the project's recorded upstream reliance policy, rather than
inventing a separate license for generated translations.

Amadeus's preparation code and corrections follow the project's root
AGPL-3.0 license. The pack includes that complete text as
`Amadeus-AGPL-3.0.txt`, in addition to the upstream notices. Do not remove
upstream attribution or relabel the upstream work as solely Amadeus-authored.

## Local derivative and distribution boundary

`tools/prepare_japanese_dictionary.py` accepts only the pinned source contents
(after newline normalization), then applies the separately tracked
`tools/data/japanese_dictionary_corrections.csv`. The recipe records each
correction, the source revision, the source hash, and the source-chain limits.
Preparation additionally normalizes Unicode reading fields, untranslated
initialism readings and native mora counts without moving valid accent nuclei,
then removes only identical full rows. Different lexical readings remain.
The generated `provenance.json` additionally identifies the derivative's
version, hash, entry count, normalization rules and repair counters.

Version 2026.10.20.3 admits only exact word/reading pairs in the hash-pinned
`tools/data/japanese_dictionary_reviewed_readings.json`, after base ownership
and CMU quarantine. The smaller candidate has 129 rows / 127 distinct words.
The ledger is an individual model-assisted lexical candidate check, not
independent human/native-speaker or listening sign-off. Unreviewed source rows
and historical corrections are research evidence, not release approval.

The historical 317-row review comprises 94 lexical/script/annotation repairs,
65 representation repairs and 158 unconfirmed readings. Those unconfirmed
rows remain in research inputs; release admission now requires the separate
checked ledger. The source-backed `let's → レッツ` repair reuses the existing
`lets` annotation and CMU phones. `proposer` has a provisional flat accent.
Neither native compilation nor these lexical checks establish accent quality.

Base/CMU hashes, ledger identity, accepted readings, exclusion counts and an
excluded-row-set hash are in provenance; rejected readings are not shipped in
that metadata. Source, correction and ledger hashes reproduce the selection.
The CMU screen reproduces the 1,557 direct flags plus 112 variants, including
false positives. It can miss interior/vowel shifts and is not a correctness
oracle. Every shipped pair must pass ledger membership independently of it.

The prepared CSV and frontend share full-token boundaries on retained Latin
entries. Only complete registered official tokens are marked. Original costs
are preserved; there are no runtime per-word reading exceptions. This CSV
requires the matching frontend integration, not just native registration.
Old full-data candidates without a checked ledger are retired and rejected.

The production gate checks every retained word in three contexts (381), all
129 row memberships, 303 common English controls (190 unlisted fallbacks), 52
baseline controls, 32 extra units and 814 base-owned words in three contexts.
These checks do not establish exhaustive lexical accuracy. Independent review
and human listening acceptance remain outstanding; this candidate is local.

The upstream default branch was rechecked on 2026-10-20 at
`b4570b876f5c086819ba84e3215472733c6a7b70`: the CSV still has the same Git blob
and contents as the pinned source above.

The CSV and notices are distributed separately as the optional
`japanese-pronunciation` asset pack. Large generated pack files remain outside
Git/source archives. No compiled `user.dict`, local `userdict.md5`, personal
CSV, model weights, or recordings are included in this pack. Compilation uses
the installed pyopenjtalk-plus runtime and writes only disposable cache files.
Updates do not overwrite the optional personal `assets/text/ja/user.csv` or
the existing legacy directory.
21 changes: 21 additions & 0 deletions LICENSES/TSQYOMI-MIT.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Aivis Project

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.
29 changes: 29 additions & 0 deletions LICENSES/TSQYOMI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Optional tsqyomi v5 Japanese context-reading model

Source: https://huggingface.co/tsukumijima/tsqyomi-models

Pinned revision: `81add61ddba9669d328e307c883d05d77d60f5f4`, directory `v5/`.
The unchanged ONNX model, tokenizer and metadata hashes are recorded in
`config/japanese_reading.py` and in the generated pack provenance.

The upstream model card declares `license: mit` and `library_name: onnxruntime`.
It identifies these files as internal model assets for pyopenjtalk-plus and asks
consumers to use that library. Amadeus uses its native candidate-selection and
NJD APIs. The repository's `LICENSE.txt` supplies the full MIT notice, including
`Copyright (c) 2026 Aivis Project`; `MIT.txt` retains that notice verbatim. This
record relies on the immediate distributor's explicit model license declaration;
it does not establish the exact training-data provenance.

Model-card permalink:
https://huggingface.co/tsukumijima/tsqyomi-models/blob/81add61ddba9669d328e307c883d05d77d60f5f4/README.md

License permalink:
https://huggingface.co/tsukumijima/tsqyomi-models/blob/81add61ddba9669d328e307c883d05d77d60f5f4/LICENSE.txt

Upstream frontend: https://github.com/tsukumijima/pyopenjtalk-plus

The optional `japanese-context-reading` pack contains only the three unchanged
model assets, provenance, this notice and the MIT terms. No voice recordings,
personal dictionary or executable Python code is bundled. Startup never
downloads assets. This is a contextual reading selector, not an emotion planner
or a guarantee of correct readings for every Japanese sentence.
Loading
Loading