From 22852ba6e36faaf2589b458e731c701e24f9dc9d Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Tue, 22 Aug 2023 14:46:27 +0900 Subject: [PATCH 01/38] start new dev cycle --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9563649..e474aab 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ platform_is_windows = sys.platform == "win32" -version = "0.3.2" +version = "0.3.3-dev" min_cython_ver = "0.21.0" try: From df77b13783a51826991897874564b3f1a2fc9b54 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 4 Nov 2023 09:58:13 +0900 Subject: [PATCH 02/38] test --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d5b34cc..d64a2f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,6 @@ enable_isort = true enable_mypy = false # TODO: enable mypy in the future mypy_preset = "strict" line_length = 88 -py_version = "py37" +py_version = "py38" [[tool.pysen.lint.mypy_targets]] paths = ["."] From c1377bcbe1ed983716709f5c5a1ba054dbc44b63 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sun, 5 Nov 2023 11:14:45 +0900 Subject: [PATCH 03/38] update submodule open_jtalk --- lib/open_jtalk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/open_jtalk b/lib/open_jtalk index ce06fc5..aade593 160000 --- a/lib/open_jtalk +++ b/lib/open_jtalk @@ -1 +1 @@ -Subproject commit ce06fc565eecbf57a698dacde935f2060a60d627 +Subproject commit aade59307b86e8788edf2dd72642e00fab800539 From e74b4e5c930848f1c43976eb8524c1036948d592 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sun, 5 Nov 2023 11:18:21 +0900 Subject: [PATCH 04/38] remove cython upper bound --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d64a2f8..3468733 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ "setuptools=0.28.0,<3.0.0", + "cython>=0.28.0", "cmake", "numpy>=1.25.0; python_version>='3.9'", "oldest-supported-numpy; python_version<'3.9'", From f273bd5f587ef15ae4653c5066dc5190e3363a96 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sun, 5 Nov 2023 11:26:28 +0900 Subject: [PATCH 05/38] prep for v0.3.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e474aab..fa71142 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ platform_is_windows = sys.platform == "win32" -version = "0.3.3-dev" +version = "0.3.3" min_cython_ver = "0.21.0" try: From 6928f45deb17b857e4535559f293077a9d581a94 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sun, 5 Nov 2023 11:32:48 +0900 Subject: [PATCH 06/38] start new dev cycle --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fa71142..a264976 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ platform_is_windows = sys.platform == "win32" -version = "0.3.3" +version = "0.3.4-dev" min_cython_ver = "0.21.0" try: From b0f02502247569f2199f1b669b1b6faeadfd5602 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 18 Nov 2023 21:45:40 +0900 Subject: [PATCH 07/38] Add user dic support based on https://github.com/r9y9/pyopenjtalk/pull/26 --- pyopenjtalk/__init__.py | 42 +++++++++++++++++++++++ pyopenjtalk/openjtalk.pyx | 60 ++++++++++++++++++++++++++++++--- pyopenjtalk/openjtalk/mecab.pxd | 11 ++++++ tests/test_openjtalk.py | 29 ++++++++++++++++ 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/pyopenjtalk/__init__.py b/pyopenjtalk/__init__.py index 299ac29..72f5db5 100644 --- a/pyopenjtalk/__init__.py +++ b/pyopenjtalk/__init__.py @@ -19,6 +19,7 @@ from .htsengine import HTSEngine from .openjtalk import OpenJTalk +from .openjtalk import mecab_dict_index as _mecab_dict_index from .utils import merge_njd_marine_features # Dictionary directory @@ -224,3 +225,44 @@ def make_label(njd_features): _lazy_init() _global_jtalk = OpenJTalk(dn_mecab=OPEN_JTALK_DICT_DIR) return _global_jtalk.make_label(njd_features) + + +def mecab_dict_index(path, out_path, dn_mecab=None): + """Create user dictionary + + Args: + path (str): path to user csv + out_path (str): path to output dictionary + dn_mecab (optional. str): path to mecab dictionary + """ + global _global_jtalk + if _global_jtalk is None: + _lazy_init() + if not exists(path): + raise FileNotFoundError("no such file or directory: %s" % path) + if dn_mecab is None: + dn_mecab = OPEN_JTALK_DICT_DIR + r = _mecab_dict_index(dn_mecab, path.encode("utf-8"), out_path.encode("utf-8")) + + # NOTE: mecab load returns 1 if success, but mecab_dict_index return the opposite + # yeah it's confusing... + if r != 0: + raise RuntimeError("Failed to create user dictionary") + + +def update_global_jtalk_with_user_dict(path): + """Update global openjtalk instance with the user dictionary + + Note that this will change the global state of the openjtalk module. + + Args: + path (str): path to user dictionary + """ + global _global_jtalk + if _global_jtalk is None: + _lazy_init() + if not exists(path): + raise FileNotFoundError("no such file or directory: %s" % path) + _global_jtalk = OpenJTalk( + dn_mecab=OPEN_JTALK_DICT_DIR, userdic=path.encode("utf-8") + ) diff --git a/pyopenjtalk/openjtalk.pyx b/pyopenjtalk/openjtalk.pyx index 96505b8..291311e 100644 --- a/pyopenjtalk/openjtalk.pyx +++ b/pyopenjtalk/openjtalk.pyx @@ -9,9 +9,12 @@ np.import_array() cimport cython from libc.stdlib cimport calloc +from libc.string cimport strlen from .openjtalk.mecab cimport Mecab, Mecab_initialize, Mecab_load, Mecab_analysis from .openjtalk.mecab cimport Mecab_get_feature, Mecab_get_size, Mecab_refresh, Mecab_clear +from .openjtalk.mecab cimport createModel, Model, Tagger, Lattice +from .openjtalk.mecab cimport mecab_dict_index as _mecab_dict_index from .openjtalk.njd cimport NJD, NJD_initialize, NJD_refresh, NJD_print, NJD_clear from .openjtalk cimport njd as _njd from .openjtalk.jpcommon cimport JPCommon, JPCommon_initialize,JPCommon_make_label @@ -116,18 +119,52 @@ cdef feature2njd(_njd.NJD* njd, features): _njd.NJDNode_set_chain_flag(node, feature_node["chain_flag"]) _njd.NJD_push_node(njd, node) +# based on Mecab_load in impl. from mecab.cpp +cdef inline int Mecab_load_with_userdic(Mecab *m, char* dicdir, char* userdic): + if userdic == NULL or strlen(userdic) == 0: + return Mecab_load(m, dicdir) + + if m == NULL or dicdir == NULL or strlen(dicdir) == 0: + return 0 + + Mecab_clear(m) + + cdef (char*)[5] argv = ["mecab", "-d", dicdir, "-u", userdic] + cdef Model *model = createModel(5, argv) + + if model == NULL: + return 0 + m.model = model + + cdef Tagger *tagger = model.createTagger() + if tagger == NULL: + Mecab_clear(m) + return 0 + m.tagger = tagger + + cdef Lattice *lattice = model.createLattice() + if lattice == NULL: + Mecab_clear(m) + return 0 + m.lattice = lattice + + return 1 + cdef class OpenJTalk(object): """OpenJTalk Args: dn_mecab (bytes): Dictionaly path for MeCab. + userdic (bytes): Dictionary path for MeCab userdic. + This option is ignored when empty bytestring is given. + Default is empty. """ cdef Mecab* mecab cdef NJD* njd cdef JPCommon* jpcommon - def __cinit__(self, bytes dn_mecab=b"/usr/local/dic"): + def __cinit__(self, bytes dn_mecab=b"/usr/local/dic", bytes userdic=b""): self.mecab = new Mecab() self.njd = new NJD() self.jpcommon = new JPCommon() @@ -136,7 +173,7 @@ cdef class OpenJTalk(object): NJD_initialize(self.njd) JPCommon_initialize(self.jpcommon) - r = self._load(dn_mecab) + r = self._load(dn_mecab, userdic) if r != 1: self._clear() raise RuntimeError("Failed to initalize Mecab") @@ -147,8 +184,8 @@ cdef class OpenJTalk(object): NJD_clear(self.njd) JPCommon_clear(self.jpcommon) - def _load(self, bytes dn_mecab): - return Mecab_load(self.mecab, dn_mecab) + def _load(self, bytes dn_mecab, bytes userdic): + return Mecab_load_with_userdic(self.mecab, dn_mecab, userdic) def run_frontend(self, text): @@ -231,3 +268,18 @@ cdef class OpenJTalk(object): del self.mecab del self.njd del self.jpcommon + +def mecab_dict_index(bytes dn_mecab, bytes path, bytes out_path): + cdef (char*)[10] argv = [ + "mecab-dict-index", + "-d", + dn_mecab, + "-u", + out_path, + "-f", + "utf-8", + "-t", + "utf-8", + path + ] + return _mecab_dict_index(10, argv) diff --git a/pyopenjtalk/openjtalk/mecab.pxd b/pyopenjtalk/openjtalk/mecab.pxd index bd367c7..1538e05 100644 --- a/pyopenjtalk/openjtalk/mecab.pxd +++ b/pyopenjtalk/openjtalk/mecab.pxd @@ -16,3 +16,14 @@ cdef extern from "mecab.h": char **Mecab_get_feature(Mecab *m) cdef int Mecab_refresh(Mecab *m) cdef int Mecab_clear(Mecab *m) + cdef int mecab_dict_index(int argc, char **argv) + +cdef extern from "mecab.h" namespace "MeCab": + cdef cppclass Tagger: + pass + cdef cppclass Lattice: + pass + cdef cppclass Model: + Tagger *createTagger() + Lattice *createLattice() + cdef Model *createModel(int argc, char **argv) diff --git a/tests/test_openjtalk.py b/tests/test_openjtalk.py index 0f43363..f372ed6 100644 --- a/tests/test_openjtalk.py +++ b/tests/test_openjtalk.py @@ -1,3 +1,5 @@ +import tempfile + import pyopenjtalk @@ -80,3 +82,30 @@ def test_g2p_phone(): ]: p = pyopenjtalk.g2p(text, kana=False) assert p == pron + + +def test_userdic(): + for text, expected in [ + ("nnmn", "n a n a m i N"), + ("GNU", "g u n u u"), + ]: + p = pyopenjtalk.g2p(text) + assert p != expected + + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".csv") as f: + f.write("nnmn,,,1,名詞,一般,*,*,*,*,nnmn,ナナミン,ナナミン,1/4,*\n") # + f.write("GNU,,,1,名詞,一般,*,*,*,*,GNU,グヌー,グヌー,2/3,*\n") + f.flush() + + with tempfile.NamedTemporaryFile( + mode="w", encoding="utf-8", suffix=".dic" + ) as f2: + pyopenjtalk.mecab_dict_index(f.name, f2.name) + pyopenjtalk.update_global_jtalk_with_user_dict(f2.name) + + for text, expected in [ + ("nnmn", "n a n a m i N"), + ("GNU", "g u n u u"), + ]: + p = pyopenjtalk.g2p(text) + assert p == expected From e8955a6a9653d090ac82a23be8f5d42605912a48 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 18 Nov 2023 21:51:35 +0900 Subject: [PATCH 08/38] add userdict support in README --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 76b7369..6df5ee5 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,38 @@ In [3]: pyopenjtalk.g2p("こんにちは", kana=True) Out[3]: 'コンニチワ' ``` +### Create/Apply user dictionary + +1. Create a CSV file (e.g. `user.csv`) and write custom words like below: + +```csv +GNU,,,1,名詞,一般,*,*,*,*,GNU,グヌー,グヌー,2/3,* +``` + +2. Call `mecab_dict_index` to compile the CSV file. + +```python +In [1]: import pyopenjtalk + +In [2]: pyopenjtalk.mecab_dict_index("user.csv", "user.dic") +reading user.csv ... 1 +emitting double-array: 100% |###########################################| + +done! +``` + +3. Call `update_global_jtalk_with_user_dict` to apply the user dictionary. + +```python +In [3]: pyopenjtalk.g2p("GNU") +Out[3]: 'j i i e n u y u u' + +In [4]: pyopenjtalk.update_global_jtalk_with_user_dict("user.dic") + +In [5]: pyopenjtalk.g2p("GNU") +Out[5]: 'g u n u u' +``` + ### About `run_marine` option After v0.3.0, the `run_marine` option has been available for estimating the Japanese accent with the DNN-based method (see [marine](https://github.com/6gsn/marine)). If you want to use the feature, please install pyopenjtalk as below; From 67407423f64c8441b0cdecc726e06ced8dbcdf33 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 18 Nov 2023 22:11:01 +0900 Subject: [PATCH 09/38] fix tests try not to use features that work differently depending on platforms --- tests/test_data/.gitignore | 2 ++ tests/test_openjtalk.py | 17 ++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 tests/test_data/.gitignore diff --git a/tests/test_data/.gitignore b/tests/test_data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/test_data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/test_openjtalk.py b/tests/test_openjtalk.py index f372ed6..adfc24e 100644 --- a/tests/test_openjtalk.py +++ b/tests/test_openjtalk.py @@ -1,4 +1,4 @@ -import tempfile +from pathlib import Path import pyopenjtalk @@ -92,16 +92,15 @@ def test_userdic(): p = pyopenjtalk.g2p(text) assert p != expected - with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".csv") as f: - f.write("nnmn,,,1,名詞,一般,*,*,*,*,nnmn,ナナミン,ナナミン,1/4,*\n") # + user_csv = str(Path(__file__).parent / "test_data" / "user.csv") + user_dic = str(Path(__file__).parent / "test_data" / "user.dic") + + with open(user_csv, "w", encoding="utf-8") as f: + f.write("nnmn,,,1,名詞,一般,*,*,*,*,nnmn,ナナミン,ナナミン,1/4,*\n") f.write("GNU,,,1,名詞,一般,*,*,*,*,GNU,グヌー,グヌー,2/3,*\n") - f.flush() - with tempfile.NamedTemporaryFile( - mode="w", encoding="utf-8", suffix=".dic" - ) as f2: - pyopenjtalk.mecab_dict_index(f.name, f2.name) - pyopenjtalk.update_global_jtalk_with_user_dict(f2.name) + pyopenjtalk.mecab_dict_index(f.name, user_dic) + pyopenjtalk.update_global_jtalk_with_user_dict(user_dic) for text, expected in [ ("nnmn", "n a n a m i N"), From 60acd928e77575f37114aa96f0439c5ca5103ce5 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Tue, 12 Dec 2023 19:54:03 +0900 Subject: [PATCH 10/38] ENH: support python3.12 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3468733..9071b03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] requires = [ - "setuptools=66.1.0; python_version>='3.12'", + "setuptools=0.28.0", "cmake", "numpy>=1.25.0; python_version>='3.9'", From 51999484359a3ecdd3c78abc29800a684cbd7076 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:02:34 +0900 Subject: [PATCH 11/38] BLD: remove distutil from setup.py --- pyproject.toml | 3 +- setup.py | 164 ++++++++++--------------------------------------- 2 files changed, 33 insertions(+), 134 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9071b03..ce01636 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,8 @@ [build-system] requires = [ "setuptools>=66.1.0; python_version>='3.12'", - "setuptools=60.0.0; sys.platform=='win32'", + "setuptools=0.28.0", "cmake", "numpy>=1.25.0; python_version>='3.9'", diff --git a/setup.py b/setup.py index a264976..bc3d411 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,13 @@ import os import subprocess import sys -from distutils.errors import DistutilsExecError -from distutils.spawn import spawn -from distutils.version import LooseVersion from glob import glob from itertools import chain from os.path import exists, join from subprocess import run import numpy as np +import setuptools.command.build_ext import setuptools.command.build_py import setuptools.command.develop from setuptools import Extension, find_packages, setup @@ -18,57 +16,30 @@ version = "0.3.4-dev" -min_cython_ver = "0.21.0" -try: - import Cython - - ver = Cython.__version__ - _CYTHON_INSTALLED = ver >= LooseVersion(min_cython_ver) -except ImportError: - _CYTHON_INSTALLED = False - - msvc_extra_compile_args_config = [ "/source-charset:utf-8", "/execution-charset:utf-8", ] -try: - if not _CYTHON_INSTALLED: - raise ImportError("No supported version of Cython installed.") - from Cython.Distutils import build_ext - - cython = True -except ImportError: - cython = False -if cython: - ext = ".pyx" +def msvc_extra_compile_args(compile_args): + cas = set(compile_args) + xs = filter(lambda x: x not in cas, msvc_extra_compile_args_config) + return list(chain(compile_args, xs)) - def msvc_extra_compile_args(compile_args): - cas = set(compile_args) - xs = filter(lambda x: x not in cas, msvc_extra_compile_args_config) - return list(chain(compile_args, xs)) - class custom_build_ext(build_ext): - def build_extensions(self): - compiler_type_is_msvc = self.compiler.compiler_type == "msvc" - for entry in self.extensions: - if compiler_type_is_msvc: - entry.extra_compile_args = msvc_extra_compile_args( - entry.extra_compile_args - if hasattr(entry, "extra_compile_args") - else [] - ) +class custom_build_ext(setuptools.command.build_ext.build_ext): + def build_extensions(self): + compiler_type_is_msvc = self.compiler.compiler_type == "msvc" + for entry in self.extensions: + if compiler_type_is_msvc: + entry.extra_compile_args = msvc_extra_compile_args( + entry.extra_compile_args + if hasattr(entry, "extra_compile_args") + else [] + ) - build_ext.build_extensions(self) - - cmdclass = {"build_ext": custom_build_ext} -else: - ext = ".cpp" - cmdclass = {} - if not os.path.exists(join("pyopenjtalk", "openjtalk" + ext)): - raise RuntimeError("Cython is required to generate C++ code") + setuptools.command.build_ext.build_ext.build_extensions(self) def check_cmake_in_path(): @@ -107,71 +78,6 @@ def check_cmake_in_path(): path." ) - -# Workaround for `distutils.spawn` problem on Windows python < 3.9 -# See details: [bpo-39763: distutils.spawn now uses subprocess (GH-18743)] -# (https://github.com/python/cpython/commit/1ec63b62035e73111e204a0e03b83503e1c58f2e) -def test_quoted_arg_change(): - child_script = """ -import os -import sys -if len(sys.argv) > 5: - try: - os.makedirs(sys.argv[1], exist_ok=True) - with open(sys.argv[2], mode=sys.argv[3], encoding=sys.argv[4]) as fd: - fd.write(sys.argv[5]) - except OSError: - pass -""" - - try: - # write - package_build_dir = "build" - file_name = join(package_build_dir, "quoted_arg_output") - output_mode = "w" - file_encoding = "utf8" - arg_value = '"ARG"' - - spawn( - [ - sys.executable, - "-c", - child_script, - package_build_dir, - file_name, - output_mode, - file_encoding, - arg_value, - ] - ) - - # read - with open(file_name, mode="r", encoding=file_encoding) as fd: - return fd.readline() != arg_value - except (DistutilsExecError, TypeError): - return False - - -def escape_string_macro_arg(s): - return s.replace("\\", "\\\\").replace('"', '\\"') - - -def escape_macro_element(x): - (k, arg) = x - return (k, escape_string_macro_arg(arg)) if type(arg) == str else x - - -def escape_macros(macros): - return list(map(escape_macro_element, macros)) - - -custom_define_macros = ( - escape_macros - if platform_is_windows and test_quoted_arg_change() - else (lambda macros: macros) -) - - # open_jtalk sources src_top = join("lib", "open_jtalk", "src") @@ -215,21 +121,19 @@ def escape_macros(macros): ext_modules = [ Extension( name="pyopenjtalk.openjtalk", - sources=[join("pyopenjtalk", "openjtalk" + ext)] + all_src, + sources=[join("pyopenjtalk", "openjtalk.pyx")] + all_src, include_dirs=[np.get_include()] + include_dirs, extra_compile_args=[], extra_link_args=[], language="c++", - define_macros=custom_define_macros( - [ - ("HAVE_CONFIG_H", None), - ("DIC_VERSION", 102), - ("MECAB_DEFAULT_RC", '"dummy"'), - ("PACKAGE", '"open_jtalk"'), - ("VERSION", '"1.10"'), - ("CHARSET_UTF_8", None), - ] - ), + define_macros=[ + ("HAVE_CONFIG_H", None), + ("DIC_VERSION", "102"), + ("MECAB_DEFAULT_RC", '"dummy"'), + ("PACKAGE", '"open_jtalk"'), + ("VERSION", '"1.10"'), + ("CHARSET_UTF_8", None), + ], ) ] @@ -239,17 +143,15 @@ def escape_macros(macros): ext_modules += [ Extension( name="pyopenjtalk.htsengine", - sources=[join("pyopenjtalk", "htsengine" + ext)] + all_htsengine_src, + sources=[join("pyopenjtalk", "htsengine.pyx")] + all_htsengine_src, include_dirs=[np.get_include(), join(htsengine_src_top, "include")], extra_compile_args=[], extra_link_args=[], libraries=["winmm"] if platform_is_windows else [], language="c++", - define_macros=custom_define_macros( - [ - ("AUDIO_PLAY_NONE", None), - ] - ), + define_macros=[ + ("AUDIO_PLAY_NONE", None), + ], ) ] @@ -291,10 +193,6 @@ def run(self): setuptools.command.develop.develop.run(self) -cmdclass["build_py"] = build_py -cmdclass["develop"] = develop - - with open("README.md", "r", encoding="utf8") as fd: long_description = fd.read() @@ -311,7 +209,7 @@ def run(self): packages=find_packages(), package_data={"": ["htsvoice/*"]}, ext_modules=ext_modules, - cmdclass=cmdclass, + cmdclass={"build_ext": custom_build_ext, "build_py": build_py, "develop": develop}, install_requires=[ "numpy >= 1.20.0", "six", @@ -328,7 +226,7 @@ def run(self): "jupyter", ], "dev": [ - "cython >= " + min_cython_ver + ",<3.0.0", + "cython>=0.28.0", "pysen", "types-setuptools", "mypy<=0.910", From 8b942a8d8acd67db0506cc72e517f6409b3dbc4e Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Sat, 16 Dec 2023 22:51:04 +0900 Subject: [PATCH 12/38] FIX: remove legacy API --- pyopenjtalk/__init__.py | 74 ++++++++++++++++++++--------------------- setup.py | 2 +- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/pyopenjtalk/__init__.py b/pyopenjtalk/__init__.py index 72f5db5..abf503a 100644 --- a/pyopenjtalk/__init__.py +++ b/pyopenjtalk/__init__.py @@ -1,16 +1,16 @@ +import atexit import os +import sys +import tarfile +import tempfile +from contextlib import ExitStack from os.path import exists +from urllib.request import urlopen -import pkg_resources -import six -from tqdm.auto import tqdm - -if six.PY2: - from urllib import urlretrieve +if sys.version_info >= (3, 9): + from importlib.resources import as_file, files else: - from urllib.request import urlretrieve - -import tarfile + from importlib_resources import as_file, files try: from .version import __version__ # NOQA @@ -22,18 +22,26 @@ from .openjtalk import mecab_dict_index as _mecab_dict_index from .utils import merge_njd_marine_features +_file_manager = ExitStack() +atexit.register(_file_manager.close) + +_pyopenjtalk_ref = files(__name__) +_dic_dir_name = "open_jtalk_dic_utf_8-1.11" + # Dictionary directory # defaults to the package directory where the dictionary will be automatically downloaded OPEN_JTALK_DICT_DIR = os.environ.get( "OPEN_JTALK_DICT_DIR", - pkg_resources.resource_filename(__name__, "open_jtalk_dic_utf_8-1.11"), + str(_file_manager.enter_context(as_file(_pyopenjtalk_ref / _dic_dir_name))), ).encode("utf-8") _dict_download_url = "https://github.com/r9y9/open_jtalk/releases/download/v1.11.1" _DICT_URL = f"{_dict_download_url}/open_jtalk_dic_utf_8-1.11.tar.gz" # Default mei_normal.voice for HMM-based TTS -DEFAULT_HTS_VOICE = pkg_resources.resource_filename( - __name__, "htsvoice/mei_normal.htsvoice" +DEFAULT_HTS_VOICE = str( + _file_manager.enter_context( + as_file(_pyopenjtalk_ref / "htsvoice/mei_normal.htsvoice") + ) ).encode("utf-8") # Global instance of OpenJTalk @@ -45,34 +53,24 @@ _global_marine = None -# https://github.com/tqdm/tqdm#hooks-and-callbacks -class _TqdmUpTo(tqdm): # type: ignore - def update_to(self, b=1, bsize=1, tsize=None): - if tsize is not None: - self.total = tsize - return self.update(b * bsize - self.n) - - def _extract_dic(): + from tqdm.auto import tqdm + global OPEN_JTALK_DICT_DIR - filename = pkg_resources.resource_filename(__name__, "dic.tar.gz") - print('Downloading: "{}"'.format(_DICT_URL)) - with _TqdmUpTo( - unit="B", - unit_scale=True, - unit_divisor=1024, - miniters=1, - desc="dic.tar.gz", - ) as t: # all optional kwargs - urlretrieve(_DICT_URL, filename, reporthook=t.update_to) - t.total = t.n - print("Extracting tar file {}".format(filename)) - with tarfile.open(filename, mode="r|gz") as f: - f.extractall(path=pkg_resources.resource_filename(__name__, "")) - OPEN_JTALK_DICT_DIR = pkg_resources.resource_filename( - __name__, "open_jtalk_dic_utf_8-1.11" - ).encode("utf-8") - os.remove(filename) + pyopenjtalk_dir = _file_manager.enter_context(as_file(_pyopenjtalk_ref)) + with tempfile.TemporaryFile() as t: + print('Downloading: "{}"'.format(_DICT_URL)) + with urlopen(_DICT_URL) as response: + with tqdm.wrapattr( + t, "write", total=getattr(response, "length", None) + ) as tar: + for chunk in response: + tar.write(chunk) + t.seek(0) + print("Extracting tar file") + with tarfile.open(mode="r|gz", fileobj=t) as f: + f.extractall(path=pyopenjtalk_dir) + OPEN_JTALK_DICT_DIR = str(pyopenjtalk_dir / _dic_dir_name).encode("utf-8") def _lazy_init(): diff --git a/setup.py b/setup.py index a264976..a676152 100644 --- a/setup.py +++ b/setup.py @@ -313,8 +313,8 @@ def run(self): ext_modules=ext_modules, cmdclass=cmdclass, install_requires=[ + "importlib_resources; python_version<'3.9'", "numpy >= 1.20.0", - "six", "tqdm", ], tests_require=["nose", "coverage"], From 039ff284c7b4b2af0cc0b4bca46caf8a36428af9 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 22 May 2024 20:19:53 +0900 Subject: [PATCH 13/38] MAINT: Bump up `actions/checkout` ` actions/setup-python` --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f383277..42d37c1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,11 +30,11 @@ jobs: python-version: 3.9 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From ba2fcd14d5b51025889d4b22998967e5fa304da4 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 22 May 2024 22:30:46 +0900 Subject: [PATCH 14/38] FIX: skip Lint with pysen if 3.7 --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 42d37c1..ba1815c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,6 +42,7 @@ jobs: python -m pip install --upgrade pip pip install -e ".[dev]" - name: Lint with pysen + if: ${{ matrix.python-version != '3.7' }} run: | pysen run lint - name: Test with pytest From 861a792b01d5be5d86817ef05c1ba2a86e23b2d5 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 13 Jul 2024 14:03:48 +0900 Subject: [PATCH 15/38] drop python 3.7 --- .github/workflows/ci.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ba1815c..ac4b963 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,14 +16,14 @@ jobs: strategy: matrix: include: - - os: ubuntu-latest - python-version: 3.7 - os: ubuntu-latest python-version: 3.8 - os: ubuntu-latest python-version: 3.9 - os: ubuntu-latest python-version: '3.10' + - os: ubuntu-latest + python-version: '3.11' - os: macos-latest python-version: 3.9 - os: windows-latest @@ -42,7 +42,6 @@ jobs: python -m pip install --upgrade pip pip install -e ".[dev]" - name: Lint with pysen - if: ${{ matrix.python-version != '3.7' }} run: | pysen run lint - name: Test with pytest From 7fb154845a0cd2da33ae7ccbd789823960b9c5f1 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 13 Jul 2024 14:08:38 +0900 Subject: [PATCH 16/38] update open_jtalk rev https://github.com/r9y9/open_jtalk/pull/9 --- lib/open_jtalk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/open_jtalk b/lib/open_jtalk index aade593..7b8e3df 160000 --- a/lib/open_jtalk +++ b/lib/open_jtalk @@ -1 +1 @@ -Subproject commit aade59307b86e8788edf2dd72642e00fab800539 +Subproject commit 7b8e3df9930ae5a73bb943445dbc562804490e73 From 2d55a0807dc3479a0c7839bb4ea1f3851913b5da Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 13 Jul 2024 16:02:05 +0900 Subject: [PATCH 17/38] v0.3.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b0f3adf..2c2fdc6 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ platform_is_windows = sys.platform == "win32" -version = "0.3.4-dev" +version = "0.3.4" msvc_extra_compile_args_config = [ "/source-charset:utf-8", From a3e2115c52bbc71bc816fdf7ad29e11300b1db35 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Sat, 13 Jul 2024 16:04:09 +0900 Subject: [PATCH 18/38] bump new dev ver --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2c2fdc6..c232c71 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ platform_is_windows = sys.platform == "win32" -version = "0.3.4" +version = "0.3.5-dev" msvc_extra_compile_args_config = [ "/source-charset:utf-8", From c362daec7ddcd55960029af45874810fce9666fc Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:21:05 +0900 Subject: [PATCH 19/38] BLD: Declarative config Move most of the configuration from setup.py to pyproject.toml. --- .github/workflows/ci.yaml | 1 + MANIFEST.in | 7 ++- pyproject.toml | 77 ++++++++++++++++++++++++- release.sh | 2 +- setup.cfg | 1 - setup.py | 118 ++------------------------------------ 6 files changed, 86 insertions(+), 120 deletions(-) delete mode 100644 setup.cfg diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ac4b963..b46d59b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,6 +32,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + fetch-depth: 0 submodules: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 diff --git a/MANIFEST.in b/MANIFEST.in index d7063f2..f82558f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,7 @@ -include README.md LICENSE.md -recursive-include pyopenjtalk *.cpp *.pxd *.md *.pyx +include pyopenjtalk/version.py +exclude .gitignore .gitmodules .travis.yml release.sh tox.ini +prune .github/* +prune docs/** recursive-include lib *.cpp *.c *.h README LICENSE COPYING CMakeLists.txt *.in exclude lib/open_jtalk/src/mecab/src/config.h +prune lib/open_jtalk/src/build/** diff --git a/pyproject.toml b/pyproject.toml index ce01636..ec208df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ [build-system] requires = [ - "setuptools>=66.1.0; python_version>='3.12'", - "setuptools>=60.0.0; sys.platform=='win32'", - "setuptools=61.0.0", + "setuptools_scm<9", "cython>=0.28.0", "cmake", "numpy>=1.25.0; python_version>='3.9'", @@ -10,6 +9,78 @@ requires = [ ] build-backend = "setuptools.build_meta" +[project] +name = "pyopenjtalk" +dynamic = ["version"] +description = "A python wrapper for OpenJTalk" +readme = "README.md" +requires-python = ">=3.7" +license.file = "LICENSE.md" +authors = [{ name = "Ryuichi Yamamoto", email = "zryuichi@gmail.com" }] +keywords = ["OpenJTalk", "Research"] +classifiers = [ + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Cython", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "License :: OSI Approved :: MIT License", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", +] +urls.Homepage = "https://github.com/r9y9/pyopenjtalk" +dependencies = [ + "importlib_resources; python_version<'3.9'", + "numpy>=1.20.0", + "tqdm", +] + +[project.optional-dependencies] +docs = [ + "sphinx_rtd_theme", + "nbsphinx>=0.8.6", + "Jinja2>=3.0.1", + "pandoc", + "ipython", + "jupyter", +] +dev = [ + "cython>=0.28.0", + "pysen", + "types-setuptools", + "mypy<=0.910", + "black>=19.19b0,<=20.8", + "click<8.1.0", + "flake8>=3.7,<4", + "flake8-bugbear", + "isort>=4.3,<5.2.0", + "types-decorator", + "importlib-metadata<5.0", +] +test = ["pytest", "scipy"] +marine = ["marine>=0.0.5"] + +[tool.setuptools.packages] +find.namespaces = false + +[tool.setuptools.exclude-package-data] +"*" = ["*.pyx", "*.pxd"] + +[tool.setuptools_scm] +# NOTE: write_to is deprecated, but version_file is not supported in Python 3.7. +write_to = "pyopenjtalk/version.py" +write_to_template = ''' +__version__ = "{version}" +''' + [tool.pysen] version = "0.10.2" diff --git a/release.sh b/release.sh index 3dabccc..ea2ac58 100755 --- a/release.sh +++ b/release.sh @@ -14,7 +14,7 @@ TAG=$(git describe --exact-match --tags HEAD) VERSION=${TAG/v/} -PYOPENJTALK_BUILD_VERSION=$VERSION python setup.py develop sdist +python -m build --sdist echo "*** Ready to release! pyopenjtalk $TAG ***" echo "Please make sure that release verion is correct." cat pyopenjtalk/version.py diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index af57bc0..0000000 --- a/setup.cfg +++ /dev/null @@ -1 +0,0 @@ -[bdist_wheel] diff --git a/setup.py b/setup.py index c232c71..dfac91e 100644 --- a/setup.py +++ b/setup.py @@ -4,18 +4,13 @@ from glob import glob from itertools import chain from os.path import exists, join -from subprocess import run import numpy as np import setuptools.command.build_ext -import setuptools.command.build_py -import setuptools.command.develop -from setuptools import Extension, find_packages, setup +from setuptools import Extension, setup platform_is_windows = sys.platform == "win32" -version = "0.3.5-dev" - msvc_extra_compile_args_config = [ "/source-charset:utf-8", "/execution-charset:utf-8", @@ -93,7 +88,9 @@ def check_cmake_in_path(): # NOTE: The wrapped OpenJTalk does not depend on HTS_Engine, # but since HTSEngine is included in CMake's dependencies, it refers to a dummy path. - r = run(["cmake", "..", "-DHTS_ENGINE_INCLUDE_DIR=.", "-DHTS_ENGINE_LIB=dummy"]) + r = subprocess.run( + ["cmake", "..", "-DHTS_ENGINE_INCLUDE_DIR=.", "-DHTS_ENGINE_LIB=dummy"] + ) r.check_returncode() os.chdir(cwd) @@ -155,109 +152,4 @@ def check_cmake_in_path(): ) ] -# Adapted from https://github.com/pytorch/pytorch -cwd = os.path.dirname(os.path.abspath(__file__)) -if os.getenv("PYOPENJTALK_BUILD_VERSION"): - version = os.getenv("PYOPENJTALK_BUILD_VERSION") -else: - try: - sha = ( - subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=cwd) - .decode("ascii") - .strip() - ) - version += "+" + sha[:7] - except subprocess.CalledProcessError: - pass - except IOError: # FileNotFoundError for python 3 - pass - - -class build_py(setuptools.command.build_py.build_py): - def run(self): - self.create_version_file() - setuptools.command.build_py.build_py.run(self) - - @staticmethod - def create_version_file(): - global version, cwd - print("-- Building version " + version) - version_path = os.path.join(cwd, "pyopenjtalk", "version.py") - with open(version_path, "w") as f: - f.write("__version__ = '{}'\n".format(version)) - - -class develop(setuptools.command.develop.develop): - def run(self): - build_py.create_version_file() - setuptools.command.develop.develop.run(self) - - -with open("README.md", "r", encoding="utf8") as fd: - long_description = fd.read() - -setup( - name="pyopenjtalk", - version=version, - description="A python wrapper for OpenJTalk", - long_description=long_description, - long_description_content_type="text/markdown", - author="Ryuichi Yamamoto", - author_email="zryuichi@gmail.com", - url="https://github.com/r9y9/pyopenjtalk", - license="MIT", - packages=find_packages(), - package_data={"": ["htsvoice/*"]}, - ext_modules=ext_modules, - cmdclass={"build_ext": custom_build_ext, "build_py": build_py, "develop": develop}, - install_requires=[ - "importlib_resources; python_version<'3.9'", - "numpy >= 1.20.0", - "tqdm", - ], - tests_require=["nose", "coverage"], - extras_require={ - "docs": [ - "sphinx_rtd_theme", - "nbsphinx>=0.8.6", - "Jinja2>=3.0.1", - "pandoc", - "ipython", - "jupyter", - ], - "dev": [ - "cython>=0.28.0", - "pysen", - "types-setuptools", - "mypy<=0.910", - "black>=19.19b0,<=20.8", - "click<8.1.0", - "flake8>=3.7,<4", - "flake8-bugbear", - "isort>=4.3,<5.2.0", - "types-decorator", - "importlib-metadata<5.0", - ], - "test": ["pytest", "scipy"], - "marine": ["marine>=0.0.5"], - }, - classifiers=[ - "Operating System :: POSIX", - "Operating System :: Unix", - "Operating System :: MacOS", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Cython", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "License :: OSI Approved :: MIT License", - "Topic :: Scientific/Engineering", - "Topic :: Software Development", - "Intended Audience :: Science/Research", - "Intended Audience :: Developers", - ], - keywords=["OpenJTalk", "Research"], -) +setup(ext_modules=ext_modules, cmdclass={"build_ext": custom_build_ext}) From 9c7a9e1171cd6c3b43b4b62e8cb45bc451737bc1 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Fri, 25 Oct 2024 02:10:57 +0900 Subject: [PATCH 20/38] ENH: Release GIL in `mecab_dict_index` It is relatively safe to release GIL during this process. --- pyopenjtalk/openjtalk.pyx | 6 ++++-- pyopenjtalk/openjtalk/mecab.pxd | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyopenjtalk/openjtalk.pyx b/pyopenjtalk/openjtalk.pyx index 291311e..a9353ad 100644 --- a/pyopenjtalk/openjtalk.pyx +++ b/pyopenjtalk/openjtalk.pyx @@ -20,7 +20,6 @@ from .openjtalk cimport njd as _njd from .openjtalk.jpcommon cimport JPCommon, JPCommon_initialize,JPCommon_make_label from .openjtalk.jpcommon cimport JPCommon_get_label_size, JPCommon_get_label_feature from .openjtalk.jpcommon cimport JPCommon_refresh, JPCommon_clear -from .openjtalk cimport njd2jpcommon from .openjtalk.text2mecab cimport text2mecab from .openjtalk.mecab2njd cimport mecab2njd from .openjtalk.njd2jpcommon cimport njd2jpcommon @@ -282,4 +281,7 @@ def mecab_dict_index(bytes dn_mecab, bytes path, bytes out_path): "utf-8", path ] - return _mecab_dict_index(10, argv) + cdef int ret + with nogil: + ret = _mecab_dict_index(10, argv) + return ret diff --git a/pyopenjtalk/openjtalk/mecab.pxd b/pyopenjtalk/openjtalk/mecab.pxd index 1538e05..9572aa8 100644 --- a/pyopenjtalk/openjtalk/mecab.pxd +++ b/pyopenjtalk/openjtalk/mecab.pxd @@ -16,7 +16,7 @@ cdef extern from "mecab.h": char **Mecab_get_feature(Mecab *m) cdef int Mecab_refresh(Mecab *m) cdef int Mecab_clear(Mecab *m) - cdef int mecab_dict_index(int argc, char **argv) + cdef int mecab_dict_index(int argc, char **argv) nogil cdef extern from "mecab.h" namespace "MeCab": cdef cppclass Tagger: From 3f20a062584fef78ef65a98066aeae0c757ef22f Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Mon, 4 Nov 2024 01:08:04 +0900 Subject: [PATCH 21/38] ENH: Optimizations and larger scope of GIL release. --- pyopenjtalk/htsengine.pyx | 23 +++-- pyopenjtalk/htsengine/__init__.pxd | 8 +- pyopenjtalk/openjtalk.pyx | 120 ++++++++++++------------- pyopenjtalk/openjtalk/jpcommon.pxd | 32 +++---- pyopenjtalk/openjtalk/mecab.pxd | 20 ++--- pyopenjtalk/openjtalk/mecab2njd.pxd | 2 +- pyopenjtalk/openjtalk/njd.pxd | 16 ++-- pyopenjtalk/openjtalk/njd2jpcommon.pxd | 2 +- pyopenjtalk/openjtalk/text2mecab.pxd | 2 +- setup.py | 2 +- 10 files changed, 115 insertions(+), 112 deletions(-) diff --git a/pyopenjtalk/htsengine.pyx b/pyopenjtalk/htsengine.pyx index 9936a6e..2abf824 100644 --- a/pyopenjtalk/htsengine.pyx +++ b/pyopenjtalk/htsengine.pyx @@ -33,13 +33,14 @@ cdef class HTSEngine(object): HTS_Engine_initialize(self.engine) if self.load(voice) != 1: - self.clear() - raise RuntimeError("Failed to initalize HTS_Engine") + self.clear() + raise RuntimeError("Failed to initalize HTS_Engine") def load(self, bytes voice): cdef char* voices = voice cdef char ret - ret = HTS_Engine_load(self.engine, &voices, 1) + with nogil: + ret = HTS_Engine_load(self.engine, &voices, 1) return ret def get_sampling_frequency(self): @@ -85,21 +86,25 @@ cdef class HTSEngine(object): """Synthesize from strings""" cdef size_t num_lines = len(labels) cdef char **lines = malloc((num_lines + 1) * sizeof(char*)) - for n in range(len(labels)): + for n in range(num_lines): lines[n] = labels[n] - cdef char ret = HTS_Engine_synthesize_from_strings(self.engine, lines, num_lines) - free(lines) + cdef char ret + with nogil: + ret = HTS_Engine_synthesize_from_strings(self.engine, lines, num_lines) + free(lines) if ret != 1: raise RuntimeError("Failed to run synthesize_from_strings") def get_generated_speech(self): """Get generated speech""" cdef size_t nsamples = HTS_Engine_get_nsamples(self.engine) - cdef np.ndarray speech = np.zeros([nsamples], dtype=np.float64) + cdef np.ndarray speech = np.empty([nsamples], dtype=np.float64) + cdef double[:] speech_view = speech cdef size_t index - for index in range(nsamples): - speech[index] = HTS_Engine_get_generated_speech(self.engine, index) + with (nogil, cython.boundscheck(False)): + for index in range(nsamples): + speech_view[index] = HTS_Engine_get_generated_speech(self.engine, index) return speech def get_fullcontext_label_format(self): diff --git a/pyopenjtalk/htsengine/__init__.pxd b/pyopenjtalk/htsengine/__init__.pxd index c24d959..d8f4fe4 100644 --- a/pyopenjtalk/htsengine/__init__.pxd +++ b/pyopenjtalk/htsengine/__init__.pxd @@ -7,16 +7,16 @@ cdef extern from "HTS_engine.h": ctypedef _HTS_Engine HTS_Engine void HTS_Engine_initialize(HTS_Engine * engine) - char HTS_Engine_load(HTS_Engine * engine, char **voices, size_t num_voices) + char HTS_Engine_load(HTS_Engine * engine, char **voices, size_t num_voices) nogil size_t HTS_Engine_get_sampling_frequency(HTS_Engine * engine) size_t HTS_Engine_get_fperiod(HTS_Engine * engine) void HTS_Engine_refresh(HTS_Engine * engine) void HTS_Engine_clear(HTS_Engine * engine) const char *HTS_Engine_get_fullcontext_label_format(HTS_Engine * engine) - char HTS_Engine_synthesize_from_strings(HTS_Engine * engine, char **lines, size_t num_lines) + char HTS_Engine_synthesize_from_strings(HTS_Engine * engine, char **lines, size_t num_lines) nogil char HTS_Engine_synthesize_from_fn(HTS_Engine * engine, const char *fn) - double HTS_Engine_get_generated_speech(HTS_Engine * engine, size_t index) + double HTS_Engine_get_generated_speech(HTS_Engine * engine, size_t index) nogil size_t HTS_Engine_get_nsamples(HTS_Engine * engine) void HTS_Engine_set_speed(HTS_Engine * engine, double f) - void HTS_Engine_add_half_tone(HTS_Engine * engine, double f) \ No newline at end of file + void HTS_Engine_add_half_tone(HTS_Engine * engine, double f) diff --git a/pyopenjtalk/openjtalk.pyx b/pyopenjtalk/openjtalk.pyx index a9353ad..e120647 100644 --- a/pyopenjtalk/openjtalk.pyx +++ b/pyopenjtalk/openjtalk.pyx @@ -2,12 +2,6 @@ # cython: boundscheck=True, wraparound=True # cython: c_string_type=unicode, c_string_encoding=ascii -import numpy as np - -cimport numpy as np -np.import_array() - -cimport cython from libc.stdlib cimport calloc from libc.string cimport strlen @@ -54,48 +48,48 @@ cdef njd_node_get_read(_njd.NJDNode* node): cdef njd_node_get_pron(_njd.NJDNode* node): return ((_njd.NJDNode_get_pron(node))).decode("utf-8") -cdef njd_node_get_acc(_njd.NJDNode* node): +cdef int njd_node_get_acc(_njd.NJDNode* node) noexcept: return _njd.NJDNode_get_acc(node) -cdef njd_node_get_mora_size(_njd.NJDNode* node): +cdef int njd_node_get_mora_size(_njd.NJDNode* node) noexcept: return _njd.NJDNode_get_mora_size(node) cdef njd_node_get_chain_rule(_njd.NJDNode* node): return ((_njd.NJDNode_get_chain_rule(node))).decode("utf-8") -cdef njd_node_get_chain_flag(_njd.NJDNode* node): - return _njd.NJDNode_get_chain_flag(node) +cdef int njd_node_get_chain_flag(_njd.NJDNode* node) noexcept: + return _njd.NJDNode_get_chain_flag(node) cdef node2feature(_njd.NJDNode* node): - return { - "string": njd_node_get_string(node), - "pos": njd_node_get_pos(node), - "pos_group1": njd_node_get_pos_group1(node), - "pos_group2": njd_node_get_pos_group2(node), - "pos_group3": njd_node_get_pos_group3(node), - "ctype": njd_node_get_ctype(node), - "cform": njd_node_get_cform(node), - "orig": njd_node_get_orig(node), - "read": njd_node_get_read(node), - "pron": njd_node_get_pron(node), - "acc": njd_node_get_acc(node), - "mora_size": njd_node_get_mora_size(node), - "chain_rule": njd_node_get_chain_rule(node), - "chain_flag": njd_node_get_chain_flag(node), - } + return { + "string": njd_node_get_string(node), + "pos": njd_node_get_pos(node), + "pos_group1": njd_node_get_pos_group1(node), + "pos_group2": njd_node_get_pos_group2(node), + "pos_group3": njd_node_get_pos_group3(node), + "ctype": njd_node_get_ctype(node), + "cform": njd_node_get_cform(node), + "orig": njd_node_get_orig(node), + "read": njd_node_get_read(node), + "pron": njd_node_get_pron(node), + "acc": njd_node_get_acc(node), + "mora_size": njd_node_get_mora_size(node), + "chain_rule": njd_node_get_chain_rule(node), + "chain_flag": njd_node_get_chain_flag(node), + } cdef njd2feature(_njd.NJD* njd): cdef _njd.NJDNode* node = njd.head features = [] while node is not NULL: - features.append(node2feature(node)) - node = node.next + features.append(node2feature(node)) + node = node.next return features -cdef feature2njd(_njd.NJD* njd, features): +cdef void feature2njd(_njd.NJD* njd, features): cdef _njd.NJDNode* node for feature_node in features: @@ -119,7 +113,7 @@ cdef feature2njd(_njd.NJD* njd, features): _njd.NJD_push_node(njd, node) # based on Mecab_load in impl. from mecab.cpp -cdef inline int Mecab_load_with_userdic(Mecab *m, char* dicdir, char* userdic): +cdef inline int Mecab_load_with_userdic(Mecab *m, char* dicdir, char* userdic) noexcept nogil: if userdic == NULL or strlen(userdic) == 0: return Mecab_load(m, dicdir) @@ -164,29 +158,31 @@ cdef class OpenJTalk(object): cdef JPCommon* jpcommon def __cinit__(self, bytes dn_mecab=b"/usr/local/dic", bytes userdic=b""): + cdef char* _dn_mecab = dn_mecab + cdef char* _userdic = userdic + self.mecab = new Mecab() self.njd = new NJD() self.jpcommon = new JPCommon() - Mecab_initialize(self.mecab) - NJD_initialize(self.njd) - JPCommon_initialize(self.jpcommon) + with nogil: + Mecab_initialize(self.mecab) + NJD_initialize(self.njd) + JPCommon_initialize(self.jpcommon) - r = self._load(dn_mecab, userdic) - if r != 1: - self._clear() - raise RuntimeError("Failed to initalize Mecab") + r = self._load(_dn_mecab, _userdic) + if r != 1: + self._clear() + raise RuntimeError("Failed to initalize Mecab") + cdef void _clear(self) noexcept nogil: + Mecab_clear(self.mecab) + NJD_clear(self.njd) + JPCommon_clear(self.jpcommon) - def _clear(self): - Mecab_clear(self.mecab) - NJD_clear(self.njd) - JPCommon_clear(self.jpcommon) - - def _load(self, bytes dn_mecab, bytes userdic): + cdef int _load(self, char* dn_mecab, char* userdic) noexcept nogil: return Mecab_load_with_userdic(self.mecab, dn_mecab, userdic) - def run_frontend(self, text): """Run OpenJTalk's text processing frontend """ @@ -194,15 +190,17 @@ cdef class OpenJTalk(object): if isinstance(text, str): text = text.encode("utf-8") - text2mecab(buff, text) - Mecab_analysis(self.mecab, buff) - mecab2njd(self.njd, Mecab_get_feature(self.mecab), Mecab_get_size(self.mecab)) - _njd.njd_set_pronunciation(self.njd) - _njd.njd_set_digit(self.njd) - _njd.njd_set_accent_phrase(self.njd) - _njd.njd_set_accent_type(self.njd) - _njd.njd_set_unvoiced_vowel(self.njd) - _njd.njd_set_long_vowel(self.njd) + cdef const char* _text = text + with nogil: + text2mecab(buff, _text) + Mecab_analysis(self.mecab, buff) + mecab2njd(self.njd, Mecab_get_feature(self.mecab), Mecab_get_size(self.mecab)) + _njd.njd_set_pronunciation(self.njd) + _njd.njd_set_digit(self.njd) + _njd.njd_set_accent_phrase(self.njd) + _njd.njd_set_accent_type(self.njd) + _njd.njd_set_unvoiced_vowel(self.njd) + _njd.njd_set_long_vowel(self.njd) features = njd2feature(self.njd) # Note that this will release memory for njd feature @@ -215,19 +213,19 @@ cdef class OpenJTalk(object): """Make full-context label """ feature2njd(self.njd, features) - njd2jpcommon(self.jpcommon, self.njd) + with nogil: + njd2jpcommon(self.jpcommon, self.njd) - JPCommon_make_label(self.jpcommon) + JPCommon_make_label(self.jpcommon) - cdef int label_size = JPCommon_get_label_size(self.jpcommon) - cdef char** label_feature - label_feature = JPCommon_get_label_feature(self.jpcommon) + label_size = JPCommon_get_label_size(self.jpcommon) + label_feature = JPCommon_get_label_feature(self.jpcommon) labels = [] for i in range(label_size): - # This will create a copy of c string - # http://cython.readthedocs.io/en/latest/src/tutorial/strings.html - labels.append(label_feature[i]) + # This will create a copy of c string + # http://cython.readthedocs.io/en/latest/src/tutorial/strings.html + labels.append(label_feature[i]) # Note that this will release memory for label feature JPCommon_refresh(self.jpcommon) diff --git a/pyopenjtalk/openjtalk/jpcommon.pxd b/pyopenjtalk/openjtalk/jpcommon.pxd index 8e86bea..c78dd0f 100644 --- a/pyopenjtalk/openjtalk/jpcommon.pxd +++ b/pyopenjtalk/openjtalk/jpcommon.pxd @@ -4,26 +4,26 @@ from libc.stdio cimport FILE cdef extern from "jpcommon.h": cdef cppclass JPCommonNode: - char *pron - char *pos - char *ctype - char *cform - int acc - int chain_flag - void *prev - void *next + char *pron + char *pos + char *ctype + char *cform + int acc + int chain_flag + void *prev + void *next cdef cppclass JPCommon: - JPCommonNode *head - JPCommonNode *tail - void *label + JPCommonNode *head + JPCommonNode *tail + void *label - void JPCommon_initialize(JPCommon * jpcommon) + void JPCommon_initialize(JPCommon * jpcommon) nogil void JPCommon_push(JPCommon * jpcommon, JPCommonNode * node) - void JPCommon_make_label(JPCommon * jpcommon) - int JPCommon_get_label_size(JPCommon * jpcommon) - char **JPCommon_get_label_feature(JPCommon * jpcommon) + void JPCommon_make_label(JPCommon * jpcommon) nogil + int JPCommon_get_label_size(JPCommon * jpcommon) nogil + char **JPCommon_get_label_feature(JPCommon * jpcommon) nogil void JPCommon_print(JPCommon * jpcommon) void JPCommon_fprint(JPCommon * jpcommon, FILE * fp) void JPCommon_refresh(JPCommon * jpcommon) - void JPCommon_clear(JPCommon * jpcommon) + void JPCommon_clear(JPCommon * jpcommon) nogil diff --git a/pyopenjtalk/openjtalk/mecab.pxd b/pyopenjtalk/openjtalk/mecab.pxd index 9572aa8..2c31912 100644 --- a/pyopenjtalk/openjtalk/mecab.pxd +++ b/pyopenjtalk/openjtalk/mecab.pxd @@ -8,14 +8,14 @@ cdef extern from "mecab.h": void *tagger void *lattice - cdef int Mecab_initialize(Mecab *m) - cdef int Mecab_load(Mecab *m, const char *dicdir) - cdef int Mecab_analysis(Mecab *m, const char *str) + cdef int Mecab_initialize(Mecab *m) nogil + cdef int Mecab_load(Mecab *m, const char *dicdir) nogil + cdef int Mecab_analysis(Mecab *m, const char *str) nogil cdef int Mecab_print(Mecab *m) - int Mecab_get_size(Mecab *m) - char **Mecab_get_feature(Mecab *m) - cdef int Mecab_refresh(Mecab *m) - cdef int Mecab_clear(Mecab *m) + int Mecab_get_size(Mecab *m) nogil + char **Mecab_get_feature(Mecab *m) nogil + cdef int Mecab_refresh(Mecab *m) nogil + cdef int Mecab_clear(Mecab *m) nogil cdef int mecab_dict_index(int argc, char **argv) nogil cdef extern from "mecab.h" namespace "MeCab": @@ -24,6 +24,6 @@ cdef extern from "mecab.h" namespace "MeCab": cdef cppclass Lattice: pass cdef cppclass Model: - Tagger *createTagger() - Lattice *createLattice() - cdef Model *createModel(int argc, char **argv) + Tagger *createTagger() nogil + Lattice *createLattice() nogil + cdef Model *createModel(int argc, char **argv) nogil diff --git a/pyopenjtalk/openjtalk/mecab2njd.pxd b/pyopenjtalk/openjtalk/mecab2njd.pxd index be57ccc..fdfc1b0 100644 --- a/pyopenjtalk/openjtalk/mecab2njd.pxd +++ b/pyopenjtalk/openjtalk/mecab2njd.pxd @@ -3,4 +3,4 @@ from .njd cimport NJD cdef extern from "mecab2njd.h": - void mecab2njd(NJD * njd, char **feature, int size); + void mecab2njd(NJD * njd, char **feature, int size) nogil diff --git a/pyopenjtalk/openjtalk/njd.pxd b/pyopenjtalk/openjtalk/njd.pxd index 38d3887..f745f8d 100644 --- a/pyopenjtalk/openjtalk/njd.pxd +++ b/pyopenjtalk/openjtalk/njd.pxd @@ -66,7 +66,7 @@ cdef extern from "njd.h": NJDNode *head NJDNode *tail - void NJD_initialize(NJD * njd) + void NJD_initialize(NJD * njd) nogil void NJD_load(NJD * njd, const char *str) void NJD_load_from_fp(NJD * njd, FILE * fp) int NJD_get_size(NJD * njd) @@ -76,22 +76,22 @@ cdef extern from "njd.h": void NJD_fprint(NJD * njd, FILE * fp) void NJD_sprint(NJD * njd, char *buff, const char *split_code) void NJD_refresh(NJD * njd) - void NJD_clear(NJD * wl) + void NJD_clear(NJD * wl) nogil cdef extern from "njd_set_accent_phrase.h": - void njd_set_accent_phrase(NJD * njd) + void njd_set_accent_phrase(NJD * njd) nogil cdef extern from "njd_set_accent_type.h": - void njd_set_accent_type(NJD * njd) + void njd_set_accent_type(NJD * njd) nogil cdef extern from "njd_set_digit.h": - void njd_set_digit(NJD * njd) + void njd_set_digit(NJD * njd) nogil cdef extern from "njd_set_long_vowel.h": - void njd_set_long_vowel(NJD * njd) + void njd_set_long_vowel(NJD * njd) nogil cdef extern from "njd_set_pronunciation.h": - void njd_set_pronunciation(NJD * njd) + void njd_set_pronunciation(NJD * njd) nogil cdef extern from "njd_set_unvoiced_vowel.h": - void njd_set_unvoiced_vowel(NJD * njd) + void njd_set_unvoiced_vowel(NJD * njd) nogil diff --git a/pyopenjtalk/openjtalk/njd2jpcommon.pxd b/pyopenjtalk/openjtalk/njd2jpcommon.pxd index 8309288..a84c859 100644 --- a/pyopenjtalk/openjtalk/njd2jpcommon.pxd +++ b/pyopenjtalk/openjtalk/njd2jpcommon.pxd @@ -4,4 +4,4 @@ from .jpcommon cimport JPCommon from .njd cimport NJD cdef extern from "njd2jpcommon.h": - void njd2jpcommon(JPCommon * jpcommon, NJD * njd) + void njd2jpcommon(JPCommon * jpcommon, NJD * njd) nogil diff --git a/pyopenjtalk/openjtalk/text2mecab.pxd b/pyopenjtalk/openjtalk/text2mecab.pxd index 6081757..1d26049 100644 --- a/pyopenjtalk/openjtalk/text2mecab.pxd +++ b/pyopenjtalk/openjtalk/text2mecab.pxd @@ -1,4 +1,4 @@ # distutils: language = c++ cdef extern from "text2mecab.h": - void text2mecab(char *output, const char *input) + void text2mecab(char *output, const char *input) nogil diff --git a/setup.py b/setup.py index c232c71..113b531 100644 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ def check_cmake_in_path(): Extension( name="pyopenjtalk.openjtalk", sources=[join("pyopenjtalk", "openjtalk.pyx")] + all_src, - include_dirs=[np.get_include()] + include_dirs, + include_dirs=include_dirs, extra_compile_args=[], extra_link_args=[], language="c++", From 5374f0cd0a7a8bc89662e3d6c6054f6a0d6ce8d0 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:49:29 +0900 Subject: [PATCH 22/38] ENH: Add mutex for global instance --- pyopenjtalk/__init__.py | 131 +++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 55 deletions(-) diff --git a/pyopenjtalk/__init__.py b/pyopenjtalk/__init__.py index abf503a..656c508 100644 --- a/pyopenjtalk/__init__.py +++ b/pyopenjtalk/__init__.py @@ -1,10 +1,15 @@ +from __future__ import annotations + import atexit import os import sys import tarfile import tempfile -from contextlib import ExitStack +from collections.abc import Callable, Generator +from contextlib import ExitStack, contextmanager from os.path import exists +from threading import Lock +from typing import TypeVar from urllib.request import urlopen if sys.version_info >= (3, 9): @@ -44,14 +49,6 @@ ) ).encode("utf-8") -# Global instance of OpenJTalk -_global_jtalk = None -# Global instance of HTSEngine -# mei_normal.voice is used as default -_global_htsengine = None -# Global instance of Marine -_global_marine = None - def _extract_dic(): from tqdm.auto import tqdm @@ -78,6 +75,49 @@ def _lazy_init(): _extract_dic() +_T = TypeVar("_T") + + +def _global_instance_manager( + instance_factory: Callable[[], _T] | None = None, instance: _T | None = None +) -> Callable[[], Generator[_T, None, None]]: + assert instance_factory is not None or instance is not None + _instance = instance + mutex = Lock() + + @contextmanager + def manager() -> Generator[_T, None, None]: + nonlocal _instance + with mutex: + if _instance is None: + _instance = instance_factory() + yield _instance + + return manager + + +def _jtalk_factory() -> OpenJTalk: + _lazy_init() + return OpenJTalk(dn_mecab=OPEN_JTALK_DICT_DIR) + + +def _marine_factory(): + try: + from marine.predict import Predictor + except ImportError: + raise ImportError("Please install marine by `pip install pyopenjtalk[marine]`") + return Predictor() + + +# Global instance of OpenJTalk +_global_jtalk = _global_instance_manager(_jtalk_factory) +# Global instance of HTSEngine +# mei_normal.voice is used as default +_global_htsengine = _global_instance_manager(lambda: HTSEngine(DEFAULT_HTS_VOICE)) +# Global instance of Marine +_global_marine = _global_instance_manager(_marine_factory) + + def g2p(*args, **kwargs): """Grapheme-to-phoeneme (G2P) conversion @@ -93,11 +133,8 @@ def g2p(*args, **kwargs): Returns: str or list: G2P result in 1) str if join is True 2) list if join is False. """ - global _global_jtalk - if _global_jtalk is None: - _lazy_init() - _global_jtalk = OpenJTalk(dn_mecab=OPEN_JTALK_DICT_DIR) - return _global_jtalk.g2p(*args, **kwargs) + with _global_jtalk() as jtalk: + return jtalk.g2p(*args, **kwargs) def estimate_accent(njd_features): @@ -111,21 +148,13 @@ def estimate_accent(njd_features): Returns: list: features for NJDNode with estimation results by marine. """ - global _global_marine - if _global_marine is None: - try: - from marine.predict import Predictor - except BaseException: - raise ImportError( - "Please install marine by `pip install pyopenjtalk[marine]`" - ) - _global_marine = Predictor() - from marine.utils.openjtalk_util import convert_njd_feature_to_marine_feature + with _global_marine() as marine: + from marine.utils.openjtalk_util import convert_njd_feature_to_marine_feature - marine_feature = convert_njd_feature_to_marine_feature(njd_features) - marine_results = _global_marine.predict( - [marine_feature], require_open_jtalk_format=True - ) + marine_feature = convert_njd_feature_to_marine_feature(njd_features) + marine_results = marine.predict( + [marine_feature], require_open_jtalk_format=True + ) njd_features = merge_njd_marine_features(njd_features, marine_results) return njd_features @@ -164,13 +193,11 @@ def synthesize(labels, speed=1.0, half_tone=0.0): if isinstance(labels, tuple) and len(labels) == 2: labels = labels[1] - global _global_htsengine - if _global_htsengine is None: - _global_htsengine = HTSEngine(DEFAULT_HTS_VOICE) - sr = _global_htsengine.get_sampling_frequency() - _global_htsengine.set_speed(speed) - _global_htsengine.add_half_tone(half_tone) - return _global_htsengine.synthesize(labels), sr + with _global_htsengine() as htsengine: + sr = htsengine.get_sampling_frequency() + htsengine.set_speed(speed) + htsengine.add_half_tone(half_tone) + return htsengine.synthesize(labels), sr def tts(text, speed=1.0, half_tone=0.0, run_marine=False): @@ -202,11 +229,8 @@ def run_frontend(text): Returns: list: features for NJDNode. """ - global _global_jtalk - if _global_jtalk is None: - _lazy_init() - _global_jtalk = OpenJTalk(dn_mecab=OPEN_JTALK_DICT_DIR) - return _global_jtalk.run_frontend(text) + with _global_jtalk() as jtalk: + return jtalk.run_frontend(text) def make_label(njd_features): @@ -218,11 +242,8 @@ def make_label(njd_features): Returns: list: full-context labels. """ - global _global_jtalk - if _global_jtalk is None: - _lazy_init() - _global_jtalk = OpenJTalk(dn_mecab=OPEN_JTALK_DICT_DIR) - return _global_jtalk.make_label(njd_features) + with _global_jtalk() as jtalk: + return jtalk.make_label(njd_features) def mecab_dict_index(path, out_path, dn_mecab=None): @@ -233,12 +254,11 @@ def mecab_dict_index(path, out_path, dn_mecab=None): out_path (str): path to output dictionary dn_mecab (optional. str): path to mecab dictionary """ - global _global_jtalk - if _global_jtalk is None: - _lazy_init() if not exists(path): raise FileNotFoundError("no such file or directory: %s" % path) if dn_mecab is None: + with _global_jtalk(): # call _lazy_init() + pass dn_mecab = OPEN_JTALK_DICT_DIR r = _mecab_dict_index(dn_mecab, path.encode("utf-8"), out_path.encode("utf-8")) @@ -257,10 +277,11 @@ def update_global_jtalk_with_user_dict(path): path (str): path to user dictionary """ global _global_jtalk - if _global_jtalk is None: - _lazy_init() - if not exists(path): - raise FileNotFoundError("no such file or directory: %s" % path) - _global_jtalk = OpenJTalk( - dn_mecab=OPEN_JTALK_DICT_DIR, userdic=path.encode("utf-8") - ) + with _global_jtalk(): + if not exists(path): + raise FileNotFoundError("no such file or directory: %s" % path) + _global_jtalk = _global_instance_manager( + instance=OpenJTalk( + dn_mecab=OPEN_JTALK_DICT_DIR, userdic=path.encode("utf-8") + ) + ) From 9bbebfb1a4b94d4c8bd1ea157eaf6d0ae501512f Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:25:47 +0900 Subject: [PATCH 23/38] ENH: Add lock. --- pyopenjtalk/htsengine.pyx | 28 +++++++++++++++++++++++++++- pyopenjtalk/openjtalk.pyx | 16 ++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pyopenjtalk/htsengine.pyx b/pyopenjtalk/htsengine.pyx index 2abf824..b8ed5e6 100644 --- a/pyopenjtalk/htsengine.pyx +++ b/pyopenjtalk/htsengine.pyx @@ -2,6 +2,9 @@ # cython: boundscheck=True, wraparound=True # cython: c_string_type=unicode, c_string_encoding=ascii +from contextlib import contextmanager +from threading import RLock + import numpy as np cimport numpy as np @@ -19,6 +22,17 @@ from .htsengine cimport ( HTS_Engine_get_generated_speech, HTS_Engine_get_nsamples ) +def _generate_lock_manager(): + lock = RLock() + + @contextmanager + def f(): + with lock: + yield + + return f + + cdef class HTSEngine(object): """HTSEngine @@ -26,6 +40,7 @@ cdef class HTSEngine(object): voice (bytes): File path of htsvoice. """ cdef HTS_Engine* engine + _lock_manager = _generate_lock_manager() def __cinit__(self, bytes voice=b"htsvoice/mei_normal.htsvoice"): self.engine = new HTS_Engine() @@ -36,6 +51,7 @@ cdef class HTSEngine(object): self.clear() raise RuntimeError("Failed to initalize HTS_Engine") + @_lock_manager() def load(self, bytes voice): cdef char* voices = voice cdef char ret @@ -43,15 +59,18 @@ cdef class HTSEngine(object): ret = HTS_Engine_load(self.engine, &voices, 1) return ret + @_lock_manager() def get_sampling_frequency(self): """Get sampling frequency """ return HTS_Engine_get_sampling_frequency(self.engine) + @_lock_manager() def get_fperiod(self): """Get frame period""" return HTS_Engine_get_fperiod(self.engine) + @_lock_manager() def set_speed(self, speed=1.0): """Set speed @@ -60,6 +79,7 @@ cdef class HTSEngine(object): """ HTS_Engine_set_speed(self.engine, speed) + @_lock_manager() def add_half_tone(self, half_tone=0.0): """Additional half tone in log-f0 @@ -68,6 +88,7 @@ cdef class HTSEngine(object): """ HTS_Engine_add_half_tone(self.engine, half_tone) + @_lock_manager() def synthesize(self, list labels): """Synthesize waveform from list of full-context labels @@ -82,6 +103,7 @@ cdef class HTSEngine(object): self.refresh() return x + @_lock_manager() def synthesize_from_strings(self, list labels): """Synthesize from strings""" cdef size_t num_lines = len(labels) @@ -96,6 +118,7 @@ cdef class HTSEngine(object): if ret != 1: raise RuntimeError("Failed to run synthesize_from_strings") + @_lock_manager() def get_generated_speech(self): """Get generated speech""" cdef size_t nsamples = HTS_Engine_get_nsamples(self.engine) @@ -107,13 +130,16 @@ cdef class HTSEngine(object): speech_view[index] = HTS_Engine_get_generated_speech(self.engine, index) return speech + @_lock_manager() def get_fullcontext_label_format(self): """Get full-context label format""" return (HTS_Engine_get_fullcontext_label_format(self.engine)).decode("utf-8") + @_lock_manager() def refresh(self): - HTS_Engine_refresh(self.engine) + HTS_Engine_refresh(self.engine) + @_lock_manager() def clear(self): HTS_Engine_clear(self.engine) diff --git a/pyopenjtalk/openjtalk.pyx b/pyopenjtalk/openjtalk.pyx index e120647..0113844 100644 --- a/pyopenjtalk/openjtalk.pyx +++ b/pyopenjtalk/openjtalk.pyx @@ -2,6 +2,9 @@ # cython: boundscheck=True, wraparound=True # cython: c_string_type=unicode, c_string_encoding=ascii +from contextlib import contextmanager +from threading import Lock + from libc.stdlib cimport calloc from libc.string cimport strlen @@ -143,6 +146,16 @@ cdef inline int Mecab_load_with_userdic(Mecab *m, char* dicdir, char* userdic) n return 1 +def _generate_lock_manager(): + lock = Lock() + + @contextmanager + def f(): + with lock: + yield + + return f + cdef class OpenJTalk(object): """OpenJTalk @@ -156,6 +169,7 @@ cdef class OpenJTalk(object): cdef Mecab* mecab cdef NJD* njd cdef JPCommon* jpcommon + _lock_manager = _generate_lock_manager() def __cinit__(self, bytes dn_mecab=b"/usr/local/dic", bytes userdic=b""): cdef char* _dn_mecab = dn_mecab @@ -183,6 +197,7 @@ cdef class OpenJTalk(object): cdef int _load(self, char* dn_mecab, char* userdic) noexcept nogil: return Mecab_load_with_userdic(self.mecab, dn_mecab, userdic) + @_lock_manager() def run_frontend(self, text): """Run OpenJTalk's text processing frontend """ @@ -209,6 +224,7 @@ cdef class OpenJTalk(object): return features + @_lock_manager() def make_label(self, features): """Make full-context label """ From 5f07318e5fce99ec422ab82e1a692933d8928175 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:59:31 +0900 Subject: [PATCH 24/38] Update pyproject.toml Drop py37. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec208df..c7902dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ name = "pyopenjtalk" dynamic = ["version"] description = "A python wrapper for OpenJTalk" readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" license.file = "LICENSE.md" authors = [{ name = "Ryuichi Yamamoto", email = "zryuichi@gmail.com" }] keywords = ["OpenJTalk", "Research"] From 341b3371f5de7b27f76689d9fe7e273ab3ae4143 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:59:50 +0900 Subject: [PATCH 25/38] Update pyproject.toml Drop py37. Co-authored-by: Ryuichi Yamamoto --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c7902dd..74885c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ "Programming Language :: Cython", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", From dfb2451fcd3ee97cc0ce66f4b531ef21a10e6a91 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:00:12 +0900 Subject: [PATCH 26/38] Update pyproject.toml Drop py37. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 74885c9..e9c2b40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ - "setuptools>=61.0.0", - "setuptools_scm<9", + "setuptools>=64", + "setuptools_scm>=8", "cython>=0.28.0", "cmake", "numpy>=1.25.0; python_version>='3.9'", From a8140bf9d0c248bb6c97587f4cea55846991ce39 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:00:28 +0900 Subject: [PATCH 27/38] Update pyproject.toml Drop py37. --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e9c2b40..fe0e1fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,9 +74,8 @@ find.namespaces = false "*" = ["*.pyx", "*.pxd"] [tool.setuptools_scm] -# NOTE: write_to is deprecated, but version_file is not supported in Python 3.7. -write_to = "pyopenjtalk/version.py" -write_to_template = ''' +version_file = "pyopenjtalk/version.py" +version_file_template = ''' __version__ = "{version}" ''' From 73173e82e8d61ee1707c019530d9cb4b256f3e20 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Sat, 14 Dec 2024 21:55:22 +0900 Subject: [PATCH 28/38] TST: Add test. --- tests/test_openjtalk.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/test_openjtalk.py b/tests/test_openjtalk.py index adfc24e..5a27466 100644 --- a/tests/test_openjtalk.py +++ b/tests/test_openjtalk.py @@ -1,3 +1,4 @@ +from concurrent.futures import ThreadPoolExecutor from pathlib import Path import pyopenjtalk @@ -68,7 +69,10 @@ def test_g2p_kana(): for text, pron in [ ("今日もこんにちは", "キョーモコンニチワ"), ("いやあん", "イヤーン"), - ("パソコンのとりあえず知っておきたい使い方", "パソコンノトリアエズシッテオキタイツカイカタ"), + ( + "パソコンのとりあえず知っておきたい使い方", + "パソコンノトリアエズシッテオキタイツカイカタ", + ), ]: p = pyopenjtalk.g2p(text, kana=True) assert p == pron @@ -108,3 +112,27 @@ def test_userdic(): ]: p = pyopenjtalk.g2p(text) assert p == expected + + +def test_multithreading(): + ojt = pyopenjtalk.openjtalk.OpenJTalk(pyopenjtalk.OPEN_JTALK_DICT_DIR) + texts = [ + "今日もいい天気ですね", + "こんにちは", + "マルチスレッドプログラミング", + "テストです", + "Pythonはプログラミング言語です", + "日本語テキストを音声合成します", + ] * 4 + + # Test consistency between single and multi-threaded runs + # make sure no corruptions happen in OJT internal + results_s = [ojt.run_frontend(text) for text in texts] + results_m = [] + with ThreadPoolExecutor() as e: + results_m = [i for i in e.map(ojt.run_frontend, texts)] + for s, m in zip(results_s, results_m): + assert len(s) == len(m) + for s_, m_ in zip(s, m): + # full context must exactly match + assert s_ == m_ From acfb7c0897967d8c029171abaf63318112a6d91f Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Fri, 20 Dec 2024 07:35:19 +0900 Subject: [PATCH 29/38] FIX: Fix setuptools warning. I made a mistake with PR #85. --- MANIFEST.in | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index f82558f..ac7b6d9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,6 @@ include pyopenjtalk/version.py exclude .gitignore .gitmodules .travis.yml release.sh tox.ini prune .github/* prune docs/** -recursive-include lib *.cpp *.c *.h README LICENSE COPYING CMakeLists.txt *.in +recursive-include lib *.cpp *.c *.h README COPYING CMakeLists.txt *.in exclude lib/open_jtalk/src/mecab/src/config.h prune lib/open_jtalk/src/build/** diff --git a/pyproject.toml b/pyproject.toml index fe0e1fe..d56bac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,8 +67,8 @@ dev = [ test = ["pytest", "scipy"] marine = ["marine>=0.0.5"] -[tool.setuptools.packages] -find.namespaces = false +[tool.setuptools.packages.find] +include = ["pyopenjtalk*"] [tool.setuptools.exclude-package-data] "*" = ["*.pyx", "*.pxd"] From e01c0a9a3b374ca565144e599d01d306808ee7bb Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:54:37 +0900 Subject: [PATCH 30/38] FIX: Fix or suppress the warning. --- pyopenjtalk/htsengine.pyx | 3 ++- pyopenjtalk/htsengine/__init__.pxd | 3 ++- pyopenjtalk/openjtalk.pyx | 3 ++- pyopenjtalk/openjtalk/__init__.pxd | 1 + pyopenjtalk/openjtalk/jpcommon.pxd | 1 + pyopenjtalk/openjtalk/mecab.pxd | 1 + pyopenjtalk/openjtalk/mecab2njd.pxd | 1 + pyopenjtalk/openjtalk/njd.pxd | 1 + pyopenjtalk/openjtalk/njd2jpcommon.pxd | 1 + pyopenjtalk/openjtalk/text2mecab.pxd | 1 + pyproject.toml | 4 ++-- setup.py | 16 ++++++++++++++++ 12 files changed, 31 insertions(+), 5 deletions(-) diff --git a/pyopenjtalk/htsengine.pyx b/pyopenjtalk/htsengine.pyx index 9936a6e..1bc2b6a 100644 --- a/pyopenjtalk/htsengine.pyx +++ b/pyopenjtalk/htsengine.pyx @@ -1,6 +1,7 @@ # coding: utf-8 # cython: boundscheck=True, wraparound=True # cython: c_string_type=unicode, c_string_encoding=ascii +# cython: language_level=3 import numpy as np @@ -19,7 +20,7 @@ from .htsengine cimport ( HTS_Engine_get_generated_speech, HTS_Engine_get_nsamples ) -cdef class HTSEngine(object): +cdef class HTSEngine: """HTSEngine Args: diff --git a/pyopenjtalk/htsengine/__init__.pxd b/pyopenjtalk/htsengine/__init__.pxd index c24d959..6da5bb3 100644 --- a/pyopenjtalk/htsengine/__init__.pxd +++ b/pyopenjtalk/htsengine/__init__.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 cdef extern from "HTS_engine.h": @@ -19,4 +20,4 @@ cdef extern from "HTS_engine.h": size_t HTS_Engine_get_nsamples(HTS_Engine * engine) void HTS_Engine_set_speed(HTS_Engine * engine, double f) - void HTS_Engine_add_half_tone(HTS_Engine * engine, double f) \ No newline at end of file + void HTS_Engine_add_half_tone(HTS_Engine * engine, double f) diff --git a/pyopenjtalk/openjtalk.pyx b/pyopenjtalk/openjtalk.pyx index 291311e..ad2b521 100644 --- a/pyopenjtalk/openjtalk.pyx +++ b/pyopenjtalk/openjtalk.pyx @@ -1,6 +1,7 @@ # coding: utf-8 # cython: boundscheck=True, wraparound=True # cython: c_string_type=unicode, c_string_encoding=ascii +# cython: language_level=3 import numpy as np @@ -151,7 +152,7 @@ cdef inline int Mecab_load_with_userdic(Mecab *m, char* dicdir, char* userdic): return 1 -cdef class OpenJTalk(object): +cdef class OpenJTalk: """OpenJTalk Args: diff --git a/pyopenjtalk/openjtalk/__init__.pxd b/pyopenjtalk/openjtalk/__init__.pxd index e69de29..f08f4aa 100644 --- a/pyopenjtalk/openjtalk/__init__.pxd +++ b/pyopenjtalk/openjtalk/__init__.pxd @@ -0,0 +1 @@ +# cython: language_level=3 diff --git a/pyopenjtalk/openjtalk/jpcommon.pxd b/pyopenjtalk/openjtalk/jpcommon.pxd index 8e86bea..555885a 100644 --- a/pyopenjtalk/openjtalk/jpcommon.pxd +++ b/pyopenjtalk/openjtalk/jpcommon.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 from libc.stdio cimport FILE diff --git a/pyopenjtalk/openjtalk/mecab.pxd b/pyopenjtalk/openjtalk/mecab.pxd index 1538e05..80ff86e 100644 --- a/pyopenjtalk/openjtalk/mecab.pxd +++ b/pyopenjtalk/openjtalk/mecab.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 cdef extern from "mecab.h": cdef cppclass Mecab: diff --git a/pyopenjtalk/openjtalk/mecab2njd.pxd b/pyopenjtalk/openjtalk/mecab2njd.pxd index be57ccc..a7880c2 100644 --- a/pyopenjtalk/openjtalk/mecab2njd.pxd +++ b/pyopenjtalk/openjtalk/mecab2njd.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 from .njd cimport NJD diff --git a/pyopenjtalk/openjtalk/njd.pxd b/pyopenjtalk/openjtalk/njd.pxd index 38d3887..4ffab6a 100644 --- a/pyopenjtalk/openjtalk/njd.pxd +++ b/pyopenjtalk/openjtalk/njd.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 from libc.stdio cimport FILE diff --git a/pyopenjtalk/openjtalk/njd2jpcommon.pxd b/pyopenjtalk/openjtalk/njd2jpcommon.pxd index 8309288..3bdec33 100644 --- a/pyopenjtalk/openjtalk/njd2jpcommon.pxd +++ b/pyopenjtalk/openjtalk/njd2jpcommon.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 from .jpcommon cimport JPCommon from .njd cimport NJD diff --git a/pyopenjtalk/openjtalk/text2mecab.pxd b/pyopenjtalk/openjtalk/text2mecab.pxd index 6081757..01f7efe 100644 --- a/pyopenjtalk/openjtalk/text2mecab.pxd +++ b/pyopenjtalk/openjtalk/text2mecab.pxd @@ -1,4 +1,5 @@ # distutils: language = c++ +# cython: language_level=3 cdef extern from "text2mecab.h": void text2mecab(char *output, const char *input) diff --git a/pyproject.toml b/pyproject.toml index fe0e1fe..0599f7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "setuptools>=64", "setuptools_scm>=8", - "cython>=0.28.0", + "cython>=3", "cmake", "numpy>=1.25.0; python_version>='3.9'", "oldest-supported-numpy; python_version<'3.9'", @@ -52,7 +52,7 @@ docs = [ "jupyter", ] dev = [ - "cython>=0.28.0", + "cython>=3", "pysen", "types-setuptools", "mypy<=0.910", diff --git a/setup.py b/setup.py index dfac91e..14e91d2 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,18 @@ def msvc_extra_compile_args(compile_args): return list(chain(compile_args, xs)) +msvc_define_macros_config = [ + ("_CRT_NONSTDC_NO_WARNINGS", None), + ("_CRT_SECURE_NO_WARNINGS", None), +] + + +def msvc_define_macros(macros): + mns = set([i[0] for i in macros]) + xs = filter(lambda x: x[0] not in mns, msvc_define_macros_config) + return list(chain(macros, xs)) + + class custom_build_ext(setuptools.command.build_ext.build_ext): def build_extensions(self): compiler_type_is_msvc = self.compiler.compiler_type == "msvc" @@ -33,6 +45,9 @@ def build_extensions(self): if hasattr(entry, "extra_compile_args") else [] ) + entry.define_macros = msvc_define_macros( + entry.define_macros if hasattr(entry, "define_macros") else [] + ) setuptools.command.build_ext.build_ext.build_extensions(self) @@ -148,6 +163,7 @@ def check_cmake_in_path(): language="c++", define_macros=[ ("AUDIO_PLAY_NONE", None), + ("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"), ], ) ] From 36acdff7ee968860e0c7b05b000f446f063aba1a Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Wed, 25 Dec 2024 23:57:04 +0900 Subject: [PATCH 31/38] FIX: Revert to `cython>=0.28.0` --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0599f7a..de23748 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "setuptools>=64", "setuptools_scm>=8", - "cython>=3", + "cython>=0.28.0", "cmake", "numpy>=1.25.0; python_version>='3.9'", "oldest-supported-numpy; python_version<'3.9'", From d9e08a8d0e72d6a100caf5e5aa023a018ec70f89 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:08:55 +0900 Subject: [PATCH 32/38] FIX: fix Cython version. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de23748..1168911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "setuptools>=64", "setuptools_scm>=8", - "cython>=0.28.0", + "cython>=0.29.16", "cmake", "numpy>=1.25.0; python_version>='3.9'", "oldest-supported-numpy; python_version<'3.9'", @@ -52,7 +52,7 @@ docs = [ "jupyter", ] dev = [ - "cython>=3", + "cython>=0.29.16", "pysen", "types-setuptools", "mypy<=0.910", From 0ad5462a37ef14ab6ab52454ffb82654b5ded923 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Wed, 22 Jan 2025 12:34:34 +0900 Subject: [PATCH 33/38] pypi upload by github actions confirmed it works ok with testpypi --- .github/workflows/ci.yaml | 80 ++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b46d59b..223cd4d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,12 +6,13 @@ name: Python package on: push: branches: [ master ] + tags: + - 'v*' pull_request: branches: [ master ] jobs: - build: - + test: runs-on: ${{ matrix.os }} strategy: matrix: @@ -49,3 +50,78 @@ jobs: run: | pip install pytest pytest + + build-for-publish: + name: Build distribution 📦 + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags')" + needs: test + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade build + + - name: Build a source tarball and wheel + run: python -m build . + + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + # NOTE: for now upload sdist only to prevent errors like unsupported platform tag 'linux_x86_64'. + path: dist/*.tar.gz + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build-for-publish + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/pyopenjtalk + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + + # NOTE: for testing purposes only + # publish-to-testpypi: + # name: Publish Python 🐍 distribution 📦 to TestPyPI + # needs: + # - build-for-publish + # runs-on: ubuntu-latest + # environment: + # name: testpypi + # url: https://test.pypi.org/p/pyopenjtalk + # permissions: + # id-token: write # IMPORTANT: mandatory for trusted publishing + # steps: + # - name: Download all the dists + # uses: actions/download-artifact@v4 + # with: + # name: python-package-distributions + # path: dist/ + # - name: Publish distribution 📦 to TestPyPI + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # repository-url: https://test.pypi.org/legacy/ + # verbose: true diff --git a/pyproject.toml b/pyproject.toml index 995aae7..9de1588 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,7 @@ version_file = "pyopenjtalk/version.py" version_file_template = ''' __version__ = "{version}" ''' +# local_scheme = "no-local-version" [tool.pysen] version = "0.10.2" From 4ae89cc5bd633a219d8551c40efdfc1c283207ad Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Fri, 24 Jan 2025 11:37:07 +0900 Subject: [PATCH 34/38] remove release.sh as it is no longer necessary --- release.sh | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100755 release.sh diff --git a/release.sh b/release.sh deleted file mode 100755 index ea2ac58..0000000 --- a/release.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Script for Pypi release -# 0. Make sure you are on git tag -# 1. Run the script -# 2. Upload sdist - -set -e - -script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd) -cd $script_dir - -TAG=$(git describe --exact-match --tags HEAD) - -VERSION=${TAG/v/} - -python -m build --sdist -echo "*** Ready to release! pyopenjtalk $TAG ***" -echo "Please make sure that release verion is correct." -cat pyopenjtalk/version.py -echo "Please run the following command manually:" -echo twine upload dist/pyopenjtalk-${VERSION}.tar.gz --repository-url https://upload.pypi.org/legacy/ From ae1a7298115374613666c40d377f0a5ee9551aac Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Tue, 1 Apr 2025 00:07:15 +0900 Subject: [PATCH 35/38] update open_jtalk for #99 includes fixes for cmake 4.0 that drops compatibility with older cmakes that do not specify cmake 3.5 as upper bound in cmake_minimum_version --- lib/open_jtalk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/open_jtalk b/lib/open_jtalk index 7b8e3df..462fc38 160000 --- a/lib/open_jtalk +++ b/lib/open_jtalk @@ -1 +1 @@ -Subproject commit 7b8e3df9930ae5a73bb943445dbc562804490e73 +Subproject commit 462fc38e7520aa89e4d32b2611749208528c901e From 6ee84b9994f52dc3d6eeafae6024c01ceb8236c9 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Fri, 4 Apr 2025 07:46:59 +0900 Subject: [PATCH 36/38] FIX: fix warning `release.sh`. --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index ac7b6d9..9bc9ebe 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include pyopenjtalk/version.py -exclude .gitignore .gitmodules .travis.yml release.sh tox.ini +exclude .gitignore .gitmodules .travis.yml tox.ini prune .github/* prune docs/** recursive-include lib *.cpp *.c *.h README COPYING CMakeLists.txt *.in From fd8864e76c93e3b3df4c14e91283dca0129c7b72 Mon Sep 17 00:00:00 2001 From: Ryuichi Yamamoto Date: Tue, 8 Apr 2025 10:48:23 +0900 Subject: [PATCH 37/38] simplify cmake command hts_engine is no longer necessary by default --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index b7b2658..0fb8fad 100644 --- a/setup.py +++ b/setup.py @@ -101,11 +101,7 @@ def check_cmake_in_path(): os.makedirs(build_dir, exist_ok=True) os.chdir(build_dir) - # NOTE: The wrapped OpenJTalk does not depend on HTS_Engine, - # but since HTSEngine is included in CMake's dependencies, it refers to a dummy path. - r = subprocess.run( - ["cmake", "..", "-DHTS_ENGINE_INCLUDE_DIR=.", "-DHTS_ENGINE_LIB=dummy"] - ) + r = subprocess.run(["cmake", ".."]) r.check_returncode() os.chdir(cwd) From 62d76b3ea98638096f54cf8daa5585c86c2f7f1e Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Thu, 17 Apr 2025 20:38:59 +0900 Subject: [PATCH 38/38] =?UTF-8?q?FIX:=20`unset=5Fuser=5Fdict()`=E3=82=92?= =?UTF-8?q?=E5=BE=A9=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyopenjtalk/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyopenjtalk/__init__.py b/pyopenjtalk/__init__.py index 656c508..3b3ac5c 100644 --- a/pyopenjtalk/__init__.py +++ b/pyopenjtalk/__init__.py @@ -285,3 +285,12 @@ def update_global_jtalk_with_user_dict(path): dn_mecab=OPEN_JTALK_DICT_DIR, userdic=path.encode("utf-8") ) ) + + +def unset_user_dict(): + """Stop applying user dictionary""" + global _global_jtalk + with _global_jtalk(): + _global_jtalk = _global_instance_manager( + instance=OpenJTalk(dn_mecab=OPEN_JTALK_DICT_DIR) + )