Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ユーザー辞書のインポート、エクスポートをUIに追加 #676

Merged
merged 13 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
27 changes: 26 additions & 1 deletion run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@

import soundfile
import uvicorn
from fastapi import FastAPI, Form, HTTPException, Query, Request, Response
from fastapi import (
FastAPI,
File,
Form,
HTTPException,
Query,
Request,
Response,
UploadFile,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
Expand Down Expand Up @@ -66,9 +75,11 @@
apply_word,
delete_word,
import_user_dict,
parse_dict,
read_dict,
rewrite_word,
update_dict,
user_dict_path,
)
from voicevox_engine.utility import (
ConnectBase64WavesException,
Expand Down Expand Up @@ -1028,6 +1039,13 @@ def import_user_dict_words(
traceback.print_exc()
raise HTTPException(status_code=422, detail="ユーザー辞書のインポートに失敗しました。")

@app.get("/download_dict", tags=["ユーザー辞書"])
async def download_dict():
dict_path = user_dict_path
return FileResponse(
dict_path, media_type="application/octet-stream", filename=dict_path.name
)

@app.get("/supported_devices", response_model=SupportedDevicesInfo, tags=["その他"])
def supported_devices(
core_version: Optional[str] = None,
Expand Down Expand Up @@ -1068,6 +1086,8 @@ def setting_post(
request: Request,
cors_policy_mode: Optional[str] = Form(None), # noqa: B008
allow_origin: Optional[str] = Form(None), # noqa: B008
user_dictionary_file: Optional[UploadFile] = File(None), # noqa: B008
allow_override: Optional[bool] = Form(False), # noqa: B008
My-MC marked this conversation as resolved.
Show resolved Hide resolved
):
settings = Setting(
cors_policy_mode=cors_policy_mode,
Expand All @@ -1080,6 +1100,11 @@ def setting_post(
if allow_origin is None:
allow_origin = ""

if user_dictionary_file.filename:
import_user_dict(
parse_dict(user_dictionary_file.file), override=allow_override
)

takana-v marked this conversation as resolved.
Show resolved Hide resolved
return setting_ui_template.TemplateResponse(
"ui.html",
{
Expand Down
26 changes: 25 additions & 1 deletion ui_template/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<body>
<div class="container p-3">
<form method="post">
<form method="post" enctype="multipart/form-data">
<div class="alert alert-warning" role="alert">
設定の変更の更新にはエンジンの再起動が必要です。
</div>
Expand Down Expand Up @@ -67,6 +67,30 @@
</div>
</div>

<div id="mb-3">
<label class="form-label"
>ユーザー辞書のエクスポート&インポート</label
>
<div class="form-text">辞書のエクスポートをします。</div>
<a
class="btn btn-primary m-3"
href="/download_dict"
target="_blank"
rel="noopener noreferrer"
>
My-MC marked this conversation as resolved.
Show resolved Hide resolved
エクスポート
</a>
<div class="form-text">辞書のインポートをします。</div>
<input
class="m-3 form-control"
type="file"
name="user_dictionary_file"
accept="application/json"
/>
<input class="form-check-input" type="checkbox" name="allow_override" value="True" id="allowOverride">
<label class="mb-3 form-check-label" for="allowOverride">辞書の上書きを許可する。</label>
</div>

<div
class="modal fade"
id="submitModal"
Expand Down
30 changes: 17 additions & 13 deletions voicevox_engine/user_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import threading
import traceback
from io import TextIOWrapper
from pathlib import Path
from typing import Dict, List, Optional
from uuid import UUID, uuid4
Expand Down Expand Up @@ -117,24 +118,27 @@ def update_dict(
tmp_compiled_path.unlink()


def parse_dict(dict_json: TextIOWrapper) -> Dict[str, UserDictWord]:
result = {}
for word_uuid, word in json.load(dict_json).items():
# cost2priorityで変換を行う際にcontext_idが必要のため、
# 0.12以前の要素にcontext_idがハルドタイプされていたため、
# ユメタル表裏を補完する
My-MC marked this conversation as resolved.
Show resolved Hide resolved
if word.get("context_id") is None:
word["context_id"] = part_of_speech_data[WordTypes.PROPER_NOUN].context_id
word["priority"] = cost2priority(word["context_id"], word["cost"])
del word["cost"]
result[str(UUID(word_uuid))] = UserDictWord(**word)

return result


@mutex_wrapper(mutex_user_dict)
def read_dict(user_dict_path: Path = user_dict_path) -> Dict[str, UserDictWord]:
if not user_dict_path.is_file():
return {}
with user_dict_path.open(encoding="utf-8") as f:
result = {}
for word_uuid, word in json.load(f).items():
# cost2priorityで変換を行う際にcontext_idが必要となるが、
# 0.12以前の辞書は、context_idがハードコーディングされていたためにユーザー辞書内に保管されていない
# ハードコーディングされていたcontext_idは固有名詞を意味するものなので、固有名詞のcontext_idを補完する
if word.get("context_id") is None:
word["context_id"] = part_of_speech_data[
WordTypes.PROPER_NOUN
].context_id
word["priority"] = cost2priority(word["context_id"], word["cost"])
del word["cost"]
result[str(UUID(word_uuid))] = UserDictWord(**word)

result = parse_dict(f)
return result


Expand Down