├── lib └── .gitkeep ├── src └── Wav2Vec2FBX │ ├── __main__.py │ ├── __init__.py │ ├── audio_util.py │ ├── fbx_writer.py │ └── wav2vec2fbx.py ├── main.py ├── setup.py ├── requirements.txt ├── mypy.ini ├── tox.ini ├── .gitignore ├── README.md ├── assets └── config.toml ├── pyproject.toml ├── LICENSE └── pylintrc /lib/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Wav2Vec2FBX/__main__.py: -------------------------------------------------------------------------------- 1 | from Wav2Vec2FBX.wav2vec2fbx import execute 2 | execute() 3 | -------------------------------------------------------------------------------- /src/Wav2Vec2FBX/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( # pylint: disable=unused-import # noqa 2 | wav2vec2fbx, 3 | fbx_writer, 4 | audio_util, 5 | ) 6 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | lib_dir = os.path.join(os.path.dirname(__file__), "lib") 4 | src_dir = os.path.join(os.path.dirname(__file__), "src") 5 | sys.path.append(lib_dir) 6 | sys.path.append(src_dir) 7 | 8 | import src.Wav2Vec2FBX.__main__ # noqa 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from cx_Freeze import setup, Executable 3 | 4 | 5 | build_exe_options = { 6 | "packages": [ 7 | "os", 8 | "torch", 9 | "_soundfile_data", 10 | "pydub" 11 | ], 12 | "zip_include_packages": [ 13 | ], 14 | "excludes": ["tkinter"], 15 | "include_files": [ 16 | ("assets/config.toml", "config.toml"), 17 | ("build/lib", "lib"), 18 | ("lib", "lib"), 19 | ] 20 | } 21 | 22 | base = None 23 | 24 | setup( 25 | name="Wav2Vec2FBX", 26 | packages=["Wav2Vec2FBX"], 27 | package_dir={"Wav2Vec2FBX": "src/Wav2Vec2FBX"}, 28 | version="0.1", 29 | description="Recognize speech file and convert it into animation FBX ", 30 | options={"build_exe": build_exe_options}, 31 | executables=[Executable("src/Wav2Vec2FBX/wav2vec2fbx.py", base=base, target_name="Wav2Vec2FBX")], 32 | entry_points={'console_scripts': ['Wav2Vec2FBX = src.__main__:main']} 33 | ) 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4 2 | audioread==2.1.9 3 | certifi==2021.10.8 4 | cffi==1.14.6 5 | charset-normalizer==2.0.12 6 | click==8.0.3 7 | colorama==0.4.4 8 | decorator==5.1.1 9 | filelock==3.5.1 10 | huggingface-hub==0.4.0 11 | idna==3.3 12 | importlib-metadata==4.11.1 13 | joblib==1.1.0 14 | librosa==0.9.1 15 | llvmlite==0.38.0 16 | numba==0.55.1 17 | numpy==1.21.5 18 | packaging==21.3 19 | Pillow==9.0.1 20 | pooch==1.6.0 21 | pycparser==2.20 22 | pynng==0.7.1 23 | pyparsing==3.0.7 24 | PyYAML==6.0 25 | regex==2022.1.18 26 | requests==2.27.1 27 | resampy==0.2.2 28 | sacremoses==0.0.47 29 | scikit-learn==1.0.2 30 | scipy==1.7.3 31 | six==1.16.0 32 | sniffio==1.2.0 33 | SoundFile==0.10.3.post1 34 | threadpoolctl==3.1.0 35 | tokenizers==0.11.5 36 | toml==0.10.2 37 | torch==1.10.2 38 | torchaudio==0.10.2 39 | torchvision==0.11.3 40 | tqdm==4.62.3 41 | transformers==4.16.2 42 | typing_extensions==4.1.1 43 | urllib3==1.26.8 44 | wincertstore==0.2 45 | zipp==3.7.0 46 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | # plugins = mypy_plugins/check_mypy_version.py 3 | cache_dir = .mypy_cache/normal 4 | warn_unused_configs = True 5 | warn_redundant_casts = True 6 | show_error_codes = True 7 | show_column_numbers = True 8 | check_untyped_defs = True 9 | follow_imports = silent 10 | 11 | # do not reenable this: 12 | # https://github.com/pytorch/pytorch/pull/60006#issuecomment-866130657 13 | warn_unused_ignores = False 14 | 15 | # 16 | # Note: test/ still has syntax errors so can't be added 17 | # 18 | # Typing tests is low priority, but enabling type checking on the 19 | # untyped test functions (using `--check-untyped-defs`) is still 20 | # high-value because it helps test the typing. 21 | # 22 | 23 | # exclude = torch/include/|torch/csrc/|torch/distributed/elastic/agent/server/api.py 24 | 25 | # Minimum version supported - variable annotations were introduced 26 | python_version = 3.7 27 | 28 | 29 | # 30 | # Extension modules without stubs. 31 | # 32 | 33 | # [mypy-pathlib] 34 | # ignore_errors = True 35 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = True 3 | 4 | 5 | [flake8] 6 | # it's not a bug that we aren't using all of hacking, ignore: 7 | # F812: list comprehension redefines ... 8 | # H101: Use TODO(NAME) 9 | # H202: assertRaises Exception too broad 10 | # H233: Python 3.x incompatible use of print operator 11 | # H301: one import per line 12 | # H306: imports not in alphabetical order (time, os) 13 | # H401: docstring should not start with a space 14 | # H403: multi line docstrings should end on a new line 15 | # H404: multi line docstring should start without a leading new line 16 | # H405: multi line docstring summary not separated with an empty line 17 | # H501: Do not use self.__dict__ for string formatting 18 | ignore = F812,H101,H202,H233,H301,H306,H401,H403,H404,H405,H501,E241,E123,W504,E226,E221,F841,W291,W293,W503 19 | max-line-length = 160 20 | exclude = tests/* 21 | max-complexity = 10 22 | # per-file-ignores = __init__.py: F401 # imported but unused 23 | 24 | 25 | [pydocstyle] 26 | add_ignore = D202, D413 27 | 28 | 29 | [testenv:maya] 30 | deps = 31 | pytest 32 | pytest-cov 33 | whitelist_externals = 34 | echo 35 | mayapy 36 | setenv = 37 | PYTHONDONTWRITEBYTECODE = 1 38 | PYTHONPATH={envsitepackagesdir};{toxinidir}/src 39 | PATH={envsitepackagesdir};{env:PATH} 40 | commands = 41 | "{env:MAYA_LOCATION}/bin/mayapy.exe" -m pytest \ 42 | --cov=src \ 43 | --cov-report term-missing \ 44 | -p no:warnings \ 45 | -p no:cacheprovider \ 46 | -xv \ 47 | {posargs:./tests/maya} 48 | 49 | 50 | [testenv:black] 51 | whitelist_externals = 52 | black 53 | setenv = 54 | PYTHONDONTWRITEBYTECODE = 1 55 | commands = 56 | black --line-length 88 ./python ./tests 57 | install_commands = 58 | pip3 install black[python27] 59 | 60 | 61 | [testenv:lint] 62 | deps = 63 | flake8 64 | pydocstyle 65 | setenv = 66 | PYTHONDONTWRITEBYTECODE = 1 67 | passenv = PYTHONPATH 68 | commands = 69 | python -m flake8 ./python 70 | python -m pydocstyle ./python 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/fbx* 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wav2Vec2FBX 2 | 3 | > Generate an FBX of a phoneme lip-sync animation from an sigle audio file, using Wav2Vec2 to analyze the phonemes for helps the animators starts with very basic animation. 4 | > 5 | ![alt text](https://github.com/yamahigashi/Wav2Vec2FBX/blob/doc/Screenshot_434.png?raw=true) 6 | 7 | 8 | ## Table of contents 9 | 10 | 11 | * [Installation](#installation) 12 | * [Environment](#environment) 13 | * [Clone Repository](#clone-repository) 14 | * [FBX python SDK](#fbx-python-sdk) 15 | * [Run](#run) 16 | * [Configuration](#configuration) 17 | * [Build binary](#build) 18 | * [References](#references) 19 | 20 | 21 | 22 | ## Installation 23 | 24 | #### Environment 25 | Virtual environment, Python 3.7 is highly recommended, as it is supported by the FBX Python SDK. 26 | 27 | 28 | #### Clone repository 29 | ```sh 30 | git clone https://github.com/yamahigashi/Wav2Vec2FBX.git 31 | cd Wav2Vec2FBX 32 | pip install requirements.txt 33 | ``` 34 | 35 | #### FBX python SDK 36 | https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-0 37 | download FBX SDK from autodesk and place libraries (`fbx.pyd`, `FbxCommon.py` and `fbxsip.pyd`) into `lib` folder. 38 | 39 | ## Run 40 | ```sh 41 | python main.py input_audio.wav 42 | ``` 43 | This will generate `input_audio.fbx` in the same folder as the input file. 44 | 45 | 46 | ## Configuration 47 | The behaviour can be changed by the configuration file `assets/config.toml`. 48 | 49 | #### keyframes settings 50 | 51 | ```toml 52 | [keyframes] 53 | # ipa と無口を補完するフレーム 54 | interpolation = 5 55 | 56 | # 複数口形素からなる ipa を補完するフレーム 57 | consecutive_viseme_frame = 3 58 | ``` 59 | 60 | #### audio settings section 61 | Describes settings for preprocessing an audio file. It splits the file based on the silence, and if it is still too long, splits the file based on the settings. 62 | ```toml 63 | [audio_settings] 64 | 65 | # 無音期間を判定する際の最小ミリセク (初期値 500) 66 | min_silence_len_ms = 500 67 | 68 | # 無音判定 (初期値 -36) 69 | silence_thresh_db = -36 70 | 71 | # 最長オーディオファイル。これ以上は複数に分割して処理 (初期値 5000) 72 | maximum_duration_ms = 5000 73 | ``` 74 | 75 | #### ipa to arpabet table settings 76 | The phonemes to morphemes correspondence table. The phonemes determined by Wav2Vec are mapped to oral morphemes. The list of morphonemes can be given as. 77 | ```toml 78 | [ipa_to_arpabet] 79 | 'ɔ' = ["a"] 80 | 'ɑ' = ["a"] 81 | 'i' = ["i"] 82 | # Long Vowels 83 | 'e ː' = ["e", "e"] 84 | 'o ː' = ["o", "o"] 85 | 86 | # -------- snip -------------- 87 | ``` 88 | ## Build binary using cx_Freeze 89 | You can deploy this package as binary for the environment without python using `cx_Freeze`. 90 | ```bash 91 | python setup.py build 92 | ``` 93 | This will generate binary for your platform. 94 | 95 | 96 | ## References 97 | - https://huggingface.co/docs/transformers/model_doc/wav2vec2 98 | - https://arxiv.org/abs/1904.05862 99 | - https://arxiv.org/abs/2006.11477 100 | - https://github.com/pytorch/fairseq/tree/main/examples/wav2vec#wav2vec-20 101 | -------------------------------------------------------------------------------- /src/Wav2Vec2FBX/audio_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pathlib 4 | import tempfile 5 | 6 | import pydub 7 | import pydub.utils 8 | import pydub.silence 9 | 10 | 11 | ############################################################################## 12 | 13 | if sys.version_info >= (3, 0): 14 | # For type annotation 15 | from typing import ( # NOQA: F401 pylint: disable=unused-import 16 | MutableMapping, 17 | Optional, 18 | Dict, 19 | List, 20 | Tuple, 21 | Pattern, 22 | Callable, 23 | Any, 24 | Text, 25 | Generator, 26 | Union 27 | ) 28 | ############################################################################## 29 | 30 | 31 | def split_by_silence(audio_file, min_silence_len_ms=500, silence_thresh_db=-35): 32 | # type: (pathlib.Path, int, int) -> Tuple 33 | 34 | keep_silence_ms = min_silence_len_ms 35 | 36 | audio_suffix = audio_file.suffix.split(".")[-1] 37 | if audio_suffix == "wav": 38 | sound_file = pydub.AudioSegment.from_wav(audio_file.as_posix()) 39 | else: 40 | sound_file = pydub.AudioSegment.from_file(audio_file.as_posix(), audio_suffix) 41 | 42 | audio_chunks = pydub.silence.split_on_silence( 43 | sound_file, 44 | min_silence_len=min_silence_len_ms, 45 | silence_thresh=silence_thresh_db, 46 | keep_silence=keep_silence_ms, 47 | seek_step=1, 48 | ) 49 | silences = pydub.silence.detect_silence( 50 | sound_file, 51 | min_silence_len=min_silence_len_ms, 52 | silence_thresh=silence_thresh_db, 53 | seek_step=1, 54 | ) 55 | 56 | if not audio_chunks or len(audio_chunks) == 1 or not silences: 57 | return [], [] 58 | 59 | if silences[0][0] == 0: 60 | pass 61 | else: 62 | silences.insert(0, (0, 0)) 63 | 64 | silences = [(x + keep_silence_ms if x > 0 else x, y - keep_silence_ms if y > 0 else y) for x, y in silences] 65 | 66 | return audio_chunks, silences 67 | 68 | 69 | def split_audio(audio_file, min_silence_len_ms=500, silence_thresh_db=-35, maximum_duration_ms=5000): 70 | # type: (pathlib.Path, int, int, int) -> List[Tuple[Text, float]] 71 | """Split input audio by silence and returns splitted file paths and its 72 | offset seconds. 73 | 74 | Here milliseconds 75 | """ 76 | 77 | print("Preprocess audio file begins. Split files by silence and that are too long") 78 | audio_chunks, silences = split_by_silence(audio_file, min_silence_len_ms=500, silence_thresh_db=-35) 79 | if not audio_chunks: 80 | return [(audio_file.as_posix(), 0.0)] 81 | 82 | # assert len(audio_chunks) == len(silences) # nosec 83 | 84 | results = [] 85 | chunk_count = 0 86 | tempdir = tempfile.mkdtemp(audio_file.name) 87 | for chunk, silence in zip(audio_chunks, silences): 88 | 89 | duration = len(chunk) 90 | if duration > maximum_duration_ms: 91 | average_duration_ms = duration / ((duration // maximum_duration_ms) + 1) 92 | over_chunks = pydub.utils.make_chunks(chunk, average_duration_ms) 93 | 94 | for oc in over_chunks: 95 | out_file = os.path.join(tempdir, "chunk{0}.wav".format(chunk_count)) 96 | print("exporting", out_file) 97 | oc.export(out_file, format="wav") 98 | results.append((out_file, silence[1] / 1000.)) 99 | chunk_count += 1 # noqa 100 | 101 | else: 102 | out_file = os.path.join(tempdir, "chunk{0}.wav".format(chunk_count)) 103 | print("exporting", out_file) 104 | chunk.export(out_file, format="wav") 105 | 106 | chunk_count += 1 107 | results.append((out_file, silence[1] / 1000.)) 108 | 109 | return results 110 | -------------------------------------------------------------------------------- /src/Wav2Vec2FBX/fbx_writer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pathlib 4 | 5 | import FbxCommon 6 | 7 | 8 | ############################################################################## 9 | 10 | if sys.version_info >= (3, 0): 11 | # For type annotation 12 | from typing import ( # NOQA: F401 pylint: disable=unused-import 13 | Optional, 14 | Dict, 15 | List, 16 | Tuple, 17 | Pattern, 18 | Callable, 19 | Any, 20 | Text, 21 | Generator, 22 | Union 23 | ) 24 | ############################################################################## 25 | 26 | 27 | def write(keys, fbx_path): 28 | # type: (Dict[Text, List], pathlib.Path) -> None 29 | 30 | sdk, scene = FbxCommon.InitializeSdkObjects() 31 | layer = create_base_anim_structure(scene) 32 | 33 | for node_name, keyframes in keys.items(): 34 | node, curve = add_node(sdk, scene, layer, node_name) 35 | 36 | for frame_sec, val in keyframes: 37 | curve.KeyModifyBegin() 38 | set_keyframe(curve, node, frame_sec, val) 39 | curve.KeyModifyEnd() 40 | 41 | FbxCommon.SaveScene(sdk, scene, fbx_path.as_posix()) 42 | print(f"write fbx to: {fbx_path.as_posix()}") 43 | sdk.Destroy() 44 | 45 | 46 | def create_base_anim_structure(scene): 47 | anim_stack = FbxCommon.FbxAnimStack.Create(scene, "Take 001") 48 | # anim_stack.LocalStop = FbxCommon.FbxTime(duration_sec) 49 | anim_layer = FbxCommon.FbxAnimLayer.Create(scene, "Base Layer") 50 | anim_stack.AddMember(anim_layer) 51 | 52 | return anim_layer 53 | 54 | 55 | def add_node(sdk, scene, layer, node_name): 56 | 57 | node = FbxCommon.FbxNode.Create(sdk, node_name) 58 | root = scene.GetRootNode() 59 | root.AddChild(node) 60 | 61 | prop = FbxCommon.FbxProperty.Create(node, FbxCommon.FbxDouble3DT, "Lcl Translation") 62 | curve_node = FbxCommon.FbxAnimCurveNode.CreateTypedCurveNode(prop, scene) 63 | layer.AddMember(curve_node) 64 | prop.ConnectSrcObject(curve_node) 65 | 66 | x_curve = FbxCommon.FbxAnimCurve.Create(scene, "") 67 | curve_node.ConnectToChannel(x_curve, 1) 68 | 69 | return node, x_curve 70 | 71 | 72 | def set_keyframe(curve, node, frame_sec, val): 73 | time = FbxCommon.FbxTime() 74 | time.SetSecondDouble(frame_sec) 75 | 76 | key = FbxCommon.FbxAnimCurveKey() 77 | key.Set(time, val) 78 | 79 | if val == 0.0: 80 | # set zero key to flat 81 | key.SetTangentMode(FbxCommon.FbxAnimCurveDef.eTangentUser) 82 | key.SetTangentWeightMode(FbxCommon.FbxAnimCurveDef.eWeightedNone) 83 | key.SetDataFloat(FbxCommon.FbxAnimCurveDef.eRightSlope, 0.0) 84 | key.SetDataFloat(FbxCommon.FbxAnimCurveDef.eNextLeftSlope, 0.0) 85 | key.SetDataFloat(FbxCommon.FbxAnimCurveDef.eWeights, 0.0) 86 | key.SetDataFloat(FbxCommon.FbxAnimCurveDef.eRightWeight, 0.333) 87 | key.SetDataFloat(FbxCommon.FbxAnimCurveDef.eNextLeftWeight, 0.333) 88 | 89 | curve.KeyAdd(time, key) 90 | 91 | 92 | def test(): 93 | entries = { 94 | "h": [(0.081015625, 0.0), (0.14177734375, 5.2639923095703125), (0.16203125, 5.293631315231323), (0.22279296875, 0.0)], 95 | "ow": [(0.1215234375, 0.0), (0.18228515625, 9.779699563980103), (0.243046875, 0.0), (0.5266015625, 0.0), (0.5873632812499999, 9.01805591583252), (0.648125, 0.0)], 96 | "ao": [(0.1215234375, 0.0), (0.18228515625, 5.943202495574951), (0.243046875, 0.0), (0.5266015625, 0.0), (0.5873632812499999, 4.2488298416137695), (0.648125, 0.0)], 97 | "o": [(0.1215234375, 0.0), (0.18228515625, 4.948630332946777), (0.243046875, 0.0), (0.30380859375, 0.0), (0.3645703125, 8.939062356948853), (0.42533203124999996, 0.0), (0.5266015625, 0.0), (0.5873632812499999, 4.385859489440918), (0.648125, 0.0)], 98 | "t": [(0.18228515625, 0.0), (0.243046875, 5.98027229309082), (0.2835546875, 10.0), (0.34431640625, 0.0)], 99 | "ey": [(0.30380859375, 0.0), (0.3645703125, 4.335820198059082), (0.42533203124999996, 0.0)], 100 | "y": [(0.34431640625, 0.0), (0.405078125, 5.597909927368164), (0.46583984375, 0.0)], 101 | "s": [(0.46583984375, 0.0), (0.5266015625, 10.0), (0.5873632812499999, 0.0)], 102 | "m": [(0.648125, 0.0), (0.70888671875, 5.360048294067383), (0.729140625, 8.003581523895264), (0.74939453125, 10.0), (0.81015625, 0.0)], 103 | } 104 | 105 | fbx_path = pathlib.Path(os.path.join(os.path.dirname(__file__), "test.fbx")) 106 | write(entries, fbx_path) 107 | 108 | 109 | if __name__ == "__main__": 110 | test() 111 | -------------------------------------------------------------------------------- /assets/config.toml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------- 2 | # 入力オーディオファイルにまつわる設定 3 | # ---------------------------------------------- 4 | [audio_settings] 5 | 6 | # 無音期間を判定する際の最小ミリセク (初期値 500) 7 | min_silence_len_ms = 500 8 | 9 | # 無音判定 (初期値 -36) 10 | silence_thresh_db = -36 11 | 12 | # 最長オーディオファイル。これ以上は複数に分割して処理 (初期値 5000) 13 | maximum_duration_ms = 5000 14 | 15 | #----------------------------------------------- 16 | [keyframes] 17 | # ipa と無口を補完するフレーム 18 | interpolation = 5 19 | 20 | # 複数口形素からなる ipa を補完するフレーム 21 | consecutive_viseme_frame = 3 22 | 23 | # ---------------------------------------------- 24 | # 判定した発音記号と出力するノードの対応付け 25 | # ---------------------------------------------- 26 | # 発音記号の意味は 27 | # international phonetic alphabet についての解説を参照 28 | # https://en.wikipedia.org/wiki/International_Phonetic_Alphabet 29 | # https://www.dyslexia-reading-well.com/44-phonemes-in-english.html 30 | # 31 | # `[ipa_to_arpabet]` エントリに wav から解析した音素と それをFBXで表現するときのノード の対応付けをする 32 | # ipa フルスペック必要ない場合、なんかいい感じに近い口を同じの入れておくとよい 33 | # [ipa_to_arpabet_simple] を参考 34 | [ipa_to_arpabet] 35 | 'ɔ' = ["a"] 36 | 'ɑ' = ["a"] 37 | 'i' = ["i"] 38 | 'u' = ["u"] 39 | 40 | 'ɛ' = ["e"] 41 | 'e' = ["e"] 42 | 'ɪ' = ["i"] 43 | 'ʊ' = ["u"] 44 | 'ʌ' = ["a"] 45 | 'ə' = ["a"] 46 | 'æ' = ["e"] 47 | 48 | # Long Vowels 49 | 'e ː' = ["e", "e"] 50 | 'o ː' = ["o", "o"] 51 | 'a ː' = ["a", "a"] 52 | 'i ː' = ["i", "i"] 53 | 't ː' = ["p", "p"] 54 | 'u ː' = ["u", "u"] 55 | 'ɑ ː' = ["a", "a"] 56 | 57 | # Vowels - Diphthongs 58 | 'eɪ' = ["e", "i"] 59 | 'aɪ' = ["a", "i"] 60 | 'oʊ' = ["o", "u"] 61 | 'aʊ' = ["a", "u"] 62 | 'ɔɪ' = ["o", "i"] 63 | 64 | 65 | # stops 66 | 'p' = ["p"] 67 | 'b' = ["p"] 68 | 't' = ["p"] 69 | 'd' = ["p"] 70 | 'k' = ["p"] 71 | 'g' = ["p"] 72 | # affricates 73 | 'tʃ' = ["ch"] 74 | 'dʒ' = ["ch"] 75 | # fricatives 76 | 'f' = ["f"] 77 | 'v' = ["v"] 78 | 'θ' = ["th"] 79 | 'ð' = ["th"] 80 | 's' = ["s"] 81 | 'z' = ["z"] 82 | 'ʃ' = ["s"] 83 | 'ʒ' = ["z"] 84 | 'h' = ["h"] 85 | # nasals 86 | 'm' = ["m"] 87 | 'n' = ["m"] 88 | 'ŋ' = ["m"] 89 | # liquids 90 | 'l' = ["l"] 91 | 'r' = ["r"] 92 | # r-colored vowels 93 | 'ɜr' = ["a"] 94 | 'ər' = ["a"] 95 | # semivowels 96 | 'w' = ["u"] 97 | 'y' = ["i"] 98 | # special 99 | 'ɚ' = ["a"] 100 | 'ɨ' = ["i"] 101 | 'oː' = ["o"] 102 | 'o' = ["o"] 103 | 'ʉ' = ["u"] 104 | 'c' = ["ch"] 105 | 'ɾ' = ["z"] 106 | 'l̩' = ["e"] 107 | 'm̩' = ["m"] 108 | 'n̩' = ["n"] 109 | 'j' = ["j"] 110 | 'ɾ̃' = ["n"] 111 | 'ʔ' = ["p"] 112 | 'ɹ' = ["r"] 113 | 'ʍ' = ["u"] 114 | 'ə̥' = ["a"] 115 | 'b̚' = ["b"] 116 | 'd̚' = ["z"] 117 | 'ŋ̍' = ["m"] 118 | 'ɡ̚' = ["g"] 119 | 'ɦ' = ["h"] 120 | 'k̚' = ["k"] 121 | 'p̚' = ["p"] 122 | 't̚' = ["p"] 123 | 124 | 'a' = ["a"] 125 | 'a ɪ' = ["a", "i"] 126 | 'd ʒ' = ["z"] 127 | 'e ɪ' = ["e", "i"] 128 | 'q' = ["k"] 129 | 't [' = ["p"] 130 | 't s' = ["p"] 131 | 't ʃ' = ["p"] 132 | 'x' = ["k"] 133 | 'ɕ' = ["s"] 134 | 'ɡ' = ["k"] 135 | 'ɲ' = ["n"] 136 | 'a 5' = ["a"] 137 | 'i 5' = ["u"] 138 | 'i. 5' = ["u"] 139 | 'o ʊ' = ["o"] 140 | 't ʲ' = ["p"] 141 | 'u 5' = ["o"] 142 | 'u o 5' = ["o"] 143 | 'u o ɜ' = ["o"] 144 | 'ɑ 5' = ["a"] 145 | 'ɑ u 5' = ["a"] 146 | 'ə ɜ' = ["e"] 147 | 'ɯ' = ["u"] 148 | 'i ɛ 5' = ["i"] 149 | 's.' = ["s"] 150 | 'o n ɡ 5' = ["n"] 151 | 'β' = ["v"] 152 | 153 | 'default' = ["_"] 154 | 155 | 156 | [ipa_to_arpabet_default] # 参照用 157 | 'ɔ' = ["ao"] 158 | 'ɑ' = ["aa"] 159 | 'i' = ["iy"] 160 | 'u' = ["uw"] 161 | 162 | 'ɛ' = ["eh"] # modern versions use 'e' instead of 'ɛ' 163 | 'e' = ["eh"] 164 | 'ɪ' = ["ih"] 165 | 'ʊ' = ["uh"] 166 | 'ʌ' = ["ah"] 167 | 'ə' = ["ax"] 168 | 'æ' = ["ae"] 169 | 170 | # Vowels - Diphthongs 171 | 'eɪ' = ["ey"] 172 | 'aɪ' = ["ay"] 173 | 'oʊ' = ["ow"] 174 | 'aʊ' = ["aw"] 175 | 'ɔɪ' = ["oy"] 176 | 177 | # stops 178 | 'p' = ["p"] 179 | 'b' = ["b"] 180 | 't' = ["t"] 181 | 'd' = ["d"] 182 | 'k' = ["k"] 183 | 'g' = ["g"] 184 | # affricates 185 | 'tʃ' = ["ch"] 186 | 'dʒ' = ["jh"] 187 | # fricatives 188 | 'f' = ["f"] 189 | 'v' = ["v"] 190 | 'θ' = ["th"] 191 | 'ð' = ["dh"] 192 | 's' = ["s"] 193 | 'z' = ["z"] 194 | 'ʃ' = ["sh"] 195 | 'ʒ' = ["zh"] 196 | 'h' = ["hh"] 197 | # nasals 198 | 'm' = ["m"] 199 | 'n' = ["n"] 200 | 'ŋ' = ["ng"] 201 | # liquids 202 | 'l' = ["l"] 203 | 'r' = ["r"] 204 | # r-colored vowels 205 | 'ɜr' = ["er"] 206 | 'ər' = ["axr"] 207 | # semivowels 208 | 'w' = ["w"] 209 | 'y' = 'j' 210 | # special 211 | 'ɚ' = ["axr"] 212 | 'ɨ' = ["ix"] 213 | 'oː' = ["ou"] 214 | 'o' = ["ow"] 215 | 'ʉ' = ["ux"] 216 | 'c' = ["ch"] 217 | 'ɾ' = ["dx"] 218 | 'l̩' = ["el"] 219 | 'm̩' = ["em"] 220 | 'n̩' = ["en"] 221 | 'j' = ["y"] 222 | 'ɾ̃' = ["nx"] 223 | 'ʔ' = ["q"] 224 | 'ɹ' = ["r"] 225 | 'ʍ' = ["wh"] 226 | 'ə̥' = ["ax-h"] 227 | 'b̚' = ["bcl"] 228 | 'd̚' = ["dcl"] 229 | 'ŋ̍' = ["eng"] 230 | 'ɡ̚' = ["gcl"] 231 | 'ɦ' = ["hv"] 232 | 'k̚' = ["kcl"] 233 | 'p̚' = ["pcl"] 234 | 't̚' = ["tcl"] 235 | 236 | 237 | [ipa_to_arpabet_simple] # 簡易口パク 238 | 'ɑ' = ["aa"] 239 | 'æ' = ["ae"] 240 | 'ʌ' = ["ah"] 241 | 'ɔ' = ["ao"] 242 | 'W' = ["aw"] 243 | 'ə' = ["ax"] 244 | 'ɚ' = ["axr"] 245 | 'Y' = ["ay"] 246 | 'ɛ' = ["eh"] 247 | 'ɝ' = ["er"] 248 | 'e' = ["ey"] 249 | 'ɪ' = ["ih"] 250 | 'ɨ' = ["ix"] 251 | 'i' = ["iy"] 252 | 'oː' = ["ou"] 253 | 'o' = ["ow"] 254 | 'O' = ["oy"] 255 | 'ʊ' = ["uh"] 256 | 'u' = ["uw"] 257 | 'ʉ' = ["ux"] 258 | 'b' = ["b"] 259 | 'C' = ["ch"] 260 | 'd' = ["d"] 261 | 'ð' = ["dh"] 262 | 'ɾ' = ["dx"] 263 | 'l̩' = ["el"] 264 | 'm̩' = ["em"] 265 | 'n̩' = ["en"] 266 | 'f' = ["f"] 267 | 'g' = ["g"] 268 | 'h' = ["h"] 269 | 'J' = ["jh"] 270 | 'k' = ["k"] 271 | 'l' = ["l"] 272 | 'm' = ["m"] 273 | 'n' = ["n"] 274 | 'ŋ' = ["ng"] 275 | 'ɾ̃' = ["nx"] 276 | 'p' = ["p"] 277 | 'ʔ' = ["q"] 278 | 'ɹ' = ["r"] 279 | 's' = ["s"] 280 | 'ʃ' = ["sh"] 281 | 't' = ["t"] 282 | 'θ' = ["th"] 283 | 'v' = ["v"] 284 | 'w' = ["w"] 285 | 'ʍ' = ["wh"] 286 | 'j' = ["y"] 287 | 'z' = ["z"] 288 | 'ʒ' = ["zh"] 289 | 'ə̥' = ["ax-h"] 290 | 'b̚' = ["bcl"] 291 | 'd̚' = ["dcl"] 292 | 'ŋ̍' = ["eng"] 293 | 'ɡ̚' = ["gcl"] 294 | 'ɦ' = ["hv"] 295 | 'k̚' = ["kcl"] 296 | 'p̚' = ["pcl"] 297 | 't̚' = ["tcl"] 298 | 'S' = ["epi"] 299 | 'P' = ["pau"] 300 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | profile = "black" 3 | add_imports=["from __future__ import generator_stop"] 4 | 5 | [tool.pytest.ini_options] 6 | addopts = [ 7 | "--strict-config", 8 | "--strict-markers", 9 | "--cov", 10 | "--cov-fail-under=92.11", 11 | "--cov-report=term-missing:skip-covered", 12 | ] 13 | xfail_strict = true 14 | junit_family = "xunit2" 15 | filterwarnings = ["error"] 16 | 17 | [tool.coverage.run] 18 | branch = true 19 | source_pkgs = ["modernize"] 20 | source = ["tests"] 21 | 22 | [tool.coverage.paths] 23 | source = [ 24 | ".", 25 | ".tox/*/lib/*/site-packages/", 26 | ] 27 | 28 | 29 | [tool.tox] 30 | legacy_tox_ini = """ 31 | ; tox configuration file for running tests on local dev env and Travis CI. 32 | ; 33 | ; The local dev environment will be executed against latest released Twisted. 34 | ; The coverage is reported only and local dev and not on Travis-CI as there 35 | ; we have separate reported (ex codecov.io) 36 | [tox] 37 | envlist = 38 | py{36,37,38,39},lint 39 | minversion=3.20.1 40 | isolated_build=true 41 | requires= 42 | virtualenv >= 20.1.0 43 | tox-wheel >= 0.6.0 44 | tox-gh-actions >= 2.1.0 45 | [testenv] 46 | extras = test 47 | commands = pytest {posargs} 48 | wheel = True 49 | wheel_build_env = build 50 | [testenv:build] 51 | # empty environment to build universal wheel once per tox invocation 52 | # https://github.com/ionelmc/tox-wheel#build-configuration 53 | [testenv:coveralls] 54 | passenv = GITHUB_* 55 | deps = 56 | coveralls 57 | coverage>=5.3 58 | commands = coveralls 59 | [testenv:lint] 60 | deps = pre-commit 61 | commands = pre-commit run --all-files --show-diff-on-failure {posargs} 62 | skip_install = true 63 | [testenv:release] 64 | deps = pep517 65 | whitelist_externals = 66 | cp 67 | rm 68 | commands = 69 | rm -rf {toxinidir}/dist 70 | cp -r {distdir} {toxinidir}/dist # copy the wheel built by tox-wheel 71 | {envpython} -m pep517.build --source --out-dir={toxinidir}/dist {toxinidir} 72 | """ 73 | 74 | [tool.black] 75 | line-length = 120 76 | target_version = ['py37'] 77 | include = '\.pyi?$' 78 | exclude = ''' 79 | 80 | ( 81 | /( 82 | \.eggs # exclude a few common directories in the 83 | | \.git # root of the project 84 | | \.hg 85 | | \.mypy_cache 86 | | \.tox 87 | | \.venv 88 | | _build 89 | | buck-out 90 | | build 91 | | dist 92 | )/ 93 | | foo.py # also separately exclude a file named foo.py in 94 | # the root of the project 95 | ) 96 | ''' 97 | 98 | 99 | [tool.pylint."FORMAT"] 100 | max-line-length=120 101 | 102 | [tool.pylint."MESSAGES CONTROL"] 103 | disable=["print-statement", 104 | "parameter-unpacking", 105 | "unpacking-in-except", 106 | "old-raise-syntax", 107 | "backtick", 108 | "long-suffix", 109 | "old-ne-operator", 110 | "old-octal-literal", 111 | "import-star-module-level", 112 | "non-ascii-bytes-literal", 113 | "raw-checker-failed", 114 | "bad-inline-option", 115 | "locally-disabled", 116 | "file-ignored", 117 | "suppressed-message", 118 | "useless-suppression", 119 | "deprecated-pragma", 120 | "use-symbolic-message-instead", 121 | "apply-builtin", 122 | "basestring-builtin", 123 | "buffer-builtin", 124 | "cmp-builtin", 125 | "coerce-builtin", 126 | "execfile-builtin", 127 | "file-builtin", 128 | "long-builtin", 129 | "raw_input-builtin", 130 | "reduce-builtin", 131 | "standarderror-builtin", 132 | "unicode-builtin", 133 | "xrange-builtin", 134 | "coerce-method", 135 | "delslice-method", 136 | "getslice-method", 137 | "setslice-method", 138 | "no-absolute-import", 139 | "old-division", 140 | "dict-iter-method", 141 | "dict-view-method", 142 | "next-method-called", 143 | "metaclass-assignment", 144 | "indexing-exception", 145 | "raising-string", 146 | "reload-builtin", 147 | "oct-method", 148 | "hex-method", 149 | "nonzero-method", 150 | "cmp-method", 151 | "input-builtin", 152 | "round-builtin", 153 | "intern-builtin", 154 | "unichr-builtin", 155 | "map-builtin-not-iterating", 156 | "zip-builtin-not-iterating", 157 | "range-builtin-not-iterating", 158 | "filter-builtin-not-iterating", 159 | "using-cmp-argument", 160 | "eq-without-hash", 161 | "div-method", 162 | "idiv-method", 163 | "rdiv-method", 164 | "exception-message-attribute", 165 | "invalid-str-codec", 166 | "sys-max-int", 167 | "bad-python3-import", 168 | "deprecated-string-function", 169 | "deprecated-str-translate-call", 170 | "deprecated-itertools-function", 171 | "deprecated-types-field", 172 | "next-method-defined", 173 | "dict-items-not-iterating", 174 | "dict-keys-not-iterating", 175 | "dict-values-not-iterating", 176 | "deprecated-operator-function", 177 | "deprecated-urllib-function", 178 | "xreadlines-attribute", 179 | "deprecated-sys-function", 180 | "exception-escape", 181 | "comprehension-escape", 182 | "line-too-long", 183 | "invalid-name", 184 | "broad-except", 185 | "missing-function-docstring", 186 | "missing-class-docstring", 187 | "assignment-from-no-return", 188 | "too-few-public-methods", 189 | "bad-whitespace", 190 | "import-error", 191 | "unused-variable", 192 | "no-else-return", 193 | "import-outside-toplevel", 194 | "misplaced-comparison-constant", 195 | "useless-else-on-loop", 196 | "using-constant-test", 197 | "consider-using-from-import", 198 | "wrong-import-order", 199 | "unnecessary-pass", 200 | ] 201 | 202 | 203 | [tool.pyright] 204 | # include = [] 205 | extraPaths = [ 206 | # "**/python", # wildcard does not supported here... 207 | "python", 208 | "vendor_python", 209 | "lib", 210 | "src", 211 | ] 212 | 213 | exclude = [ 214 | "**/node_modules", 215 | "**/__pycache__", 216 | ] 217 | ignore = [] 218 | # typeshedPath = "stubs" 219 | # typeshedPaths = ["stubs"] 220 | # extraPaths = ["typing"] 221 | 222 | reportUnusedImport = false 223 | reportMissingImports = true 224 | reportMissingTypeStubs = true 225 | reportMissingModuleSource = false 226 | 227 | pythonVersion = "3.7" 228 | pythonPlatform = "Windows" 229 | typeCheckingMode = "basic" 230 | 231 | # include [array of paths, optional] 232 | # exclude [array of paths, optional] 233 | # ignore [array of paths, optional] 234 | # strict [array of paths, optional] 235 | # typeshedPath [path, optional] 236 | # stubPath [path, optional] 237 | # venvPath [path, optional] 238 | # venv [string, optional] 239 | # verboseOutput [boolean] 240 | # extraPaths [array of strings, optional] 241 | # pythonVersion [string, optional] 242 | # pythonPlatform [string, optional] 243 | # executionEnvironments [array of objects, optional] 244 | # typeCheckingMode ["off", "basic", "strict"] 245 | # useLibraryCodeForTypes [boolean] 246 | # strictListInference [boolean] 247 | # strictDictionaryInference [boolean] 248 | # strictSetInference [boolean] 249 | # strictParameterNoneValue [boolean] 250 | # enableTypeIgnoreComments [boolean] 251 | # reportGeneralTypeIssues [boolean or string, optional] 252 | # reportPropertyTypeMismatch [boolean or string, optional] 253 | # reportFunctionMemberAccess [boolean or string, optional] 254 | # reportMissingImports [boolean or string, optional] 255 | # reportMissingModuleSource [boolean or string, optional] 256 | # reportMissingTypeStubs [boolean or string, optional] 257 | # reportImportCycles [boolean or string, optional] 258 | # reportUnusedImport [boolean or string, optional] 259 | # reportUnusedClass [boolean or string, optional] 260 | # reportUnusedFunction [boolean or string, optional] 261 | # reportUnusedVariable [boolean or string, optional] 262 | # reportDuplicateImport [boolean or string, optional] 263 | # reportWildcardImportFromLibrary [boolean or string, optional] 264 | # reportOptionalSubscript [boolean or string, optional] 265 | # reportOptionalMemberAccess [boolean or string, optional] 266 | # reportOptionalCall [boolean or string, optional] 267 | # reportOptionalIterable [boolean or string, optional] 268 | # reportOptionalContextManager [boolean or string, optional] 269 | # reportOptionalOperand [boolean or string, optional] 270 | # reportTypedDictNotRequiredAccess [boolean or string, optional] 271 | # reportUntypedFunctionDecorator [boolean or string, optional] 272 | # reportUntypedClassDecorator [boolean or string, optional] 273 | # reportUntypedBaseClass [boolean or string, optional] 274 | # reportUntypedNamedTuple [boolean or string, optional] 275 | # reportPrivateUsage [boolean or string, optional] 276 | # reportConstantRedefinition [boolean or string, optional] 277 | # reportIncompatibleMethodOverride [boolean or string, optional] 278 | # reportIncompatibleVariableOverride [boolean or string, optional] 279 | # reportOverlappingOverload [boolean or string, optional] 280 | # reportUninitializedInstanceVariable [boolean or string, optional] 281 | # reportInvalidStringEscapeSequence [boolean or string, optional] 282 | # reportUnknownParameterType [boolean or string, optional] 283 | # reportUnknownArgumentType [boolean or string, optional] 284 | # reportUnknownLambdaType [boolean or string, optional] 285 | # reportUnknownVariableType [boolean or string, optional] 286 | # reportUnknownMemberType [boolean or string, optional] 287 | # reportMissingTypeArgument [boolean or string, optional] 288 | # reportInvalidTypeVarUse [boolean or string, optional] 289 | # reportCallInDefaultInitializer [boolean or string, optional] 290 | # reportUnnecessaryIsInstance [boolean or string, optional] 291 | # reportUnnecessaryCast [boolean or string, optional] 292 | # reportUnnecessaryComparison [boolean or string, optional] 293 | # reportAssertAlwaysTrue [boolean or string, optional] 294 | # reportSelfClsParameterName [boolean or string, optional] 295 | # reportImplicitStringConcatenation [boolean or string, optional] 296 | # reportUndefinedVariable [boolean or string, optional] 297 | # reportUnboundVariable [boolean or string, optional] 298 | # reportInvalidStubStatement [boolean or string, optional] 299 | # reportIncompleteStub [boolean or string, optional] 300 | # reportUnsupportedDunderAll [boolean or string, optional] 301 | # reportUnusedCallResult [boolean or string, optional] 302 | # reportUnusedCoroutine [boolean or string, optional] 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/Wav2Vec2FBX/wav2vec2fbx.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import copy 4 | import contextlib 5 | import argparse 6 | import pathlib 7 | import multiprocessing 8 | import concurrent.futures 9 | 10 | import toml 11 | import torch 12 | import librosa 13 | 14 | from transformers import ( 15 | Wav2Vec2Processor, 16 | Wav2Vec2ForCTC, 17 | # Wav2Vec2PhonemeCTCTokenizer, 18 | ) 19 | 20 | if getattr(sys, 'frozen', False): 21 | # frozen 22 | from Wav2Vec2FBX import ( 23 | fbx_writer, 24 | audio_util 25 | ) 26 | else: 27 | # unfrozen 28 | from . import ( 29 | fbx_writer, 30 | audio_util 31 | ) 32 | ############################################################################## 33 | 34 | 35 | if sys.version_info >= (3, 0): 36 | # For type annotation 37 | from typing import ( # NOQA: F401 pylint: disable=unused-import 38 | MutableMapping, 39 | Optional, 40 | Dict, 41 | List, 42 | Tuple, 43 | Pattern, 44 | Callable, 45 | Any, 46 | Text, 47 | Generator, 48 | Union 49 | ) 50 | ConfType = MutableMapping[Text, Any] 51 | 52 | ############################################################################## 53 | CONFIG_NAME = "config.toml" 54 | ############################################################################## 55 | 56 | 57 | def parse_args(): 58 | # type: () -> argparse.Namespace 59 | 60 | parser = argparse.ArgumentParser() 61 | parser.add_argument('input_audiofile', type=pathlib.Path) 62 | parser.add_argument('--config_file', type=pathlib.Path) 63 | args = parser.parse_args() 64 | 65 | return args 66 | 67 | 68 | def load_config(args): 69 | # type: (argparse.Namespace) -> ConfType 70 | 71 | if args.config_file is not None: 72 | return toml.load(args.config_file) 73 | 74 | if getattr(sys, 'frozen', False): 75 | # frozen 76 | dir_ = os.path.dirname(sys.executable) 77 | else: 78 | # unfrozen 79 | dir_ = os.path.dirname(os.path.realpath(__file__)) 80 | 81 | config_paths = [ 82 | os.path.join(dir_, CONFIG_NAME), 83 | os.path.join(dir_, "../", CONFIG_NAME), 84 | os.path.join(dir_, "assets", CONFIG_NAME), 85 | os.path.join(dir_, "../assets", CONFIG_NAME), 86 | ] 87 | 88 | if "src/Wav2Vec2FBX" in dir_.replace(os.sep, "/"): 89 | config_paths.append(os.path.join(dir_, "../../assets", CONFIG_NAME)) 90 | 91 | for config_path in config_paths: 92 | if os.path.exists(config_path): 93 | return toml.load(config_path) 94 | 95 | raise Exception("config file not found {}".format(config_paths)) 96 | 97 | 98 | def load_models(): 99 | # type: () -> Tuple[Wav2Vec2Processor, Wav2Vec2ForCTC] 100 | """load model & audio and run audio through model.""" 101 | 102 | print("Start initialization of Wav2Vec2 models.") 103 | model_name = "facebook/wav2vec2-large-960h-lv60-self" 104 | model_name = "facebook/wav2vec2-lv-60-espeak-cv-ft" 105 | model_name = "facebook/wav2vec2-xlsr-53-espeak-cv-ft" 106 | 107 | with torch.no_grad(): 108 | processor = Wav2Vec2Processor.from_pretrained(model_name) 109 | model = Wav2Vec2ForCTC.from_pretrained(model_name).cpu() 110 | # tokenizer = Wav2Vec2PhonemeCTCTokenizer.from_pretrained(model_name) 111 | 112 | print("Completed initialzation of Wav2Vec2 models") 113 | 114 | return processor, model 115 | 116 | 117 | def is_silence(vals, ids): 118 | # type: (torch.Tensor, torch.Tensor) -> bool 119 | 120 | if ids[0] == 0 and vals[0] > 10.: 121 | return True 122 | 123 | if ids[0] == 0: 124 | x = vals[1] + (10. - vals[0]) / 2.5 125 | y = vals[2] + (10. - vals[0]) / 2.5 126 | 127 | if x < 5. and y < 5.: 128 | return True 129 | 130 | return False 131 | 132 | 133 | def calculate_voice_power(vals, ids): 134 | # type: (torch.Tensor, torch.Tensor) -> Tuple[float, float, float] 135 | 136 | x, y, z = 0., 0., 0. 137 | 138 | v0 = vals[0].item() 139 | v1 = vals[1].item() 140 | v2 = vals[2].item() 141 | 142 | if not isinstance(v0, float): 143 | raise 144 | if not isinstance(v1, float): 145 | raise 146 | if not isinstance(v2, float): 147 | raise 148 | 149 | # ------------------------------------------------- 150 | if ids[0] == 0: 151 | # if silence prevails, lift the others 152 | y = v2 + (10. - v0) / 2.0 # type: float 153 | z = v2 + (10. - v0) / 2.0 # type: float 154 | if y < 5.: 155 | y = 0. 156 | if z < 5.: 157 | z = 0. 158 | 159 | else: 160 | x = min(10.0, vals[0].item() * 1.5) # type: ignore 161 | y = v1 if v1 > 4. else 0. 162 | z = v2 if v2 > 4. else 0. 163 | 164 | # ------------------------------------------------- 165 | # limit total when uttered simultaneously 166 | if x > 0.: 167 | x = (((1. / (x + y + z)) * x) * 0.5) + (x * 0.5) 168 | y = (((1. / (x + y + z)) * y) * 0.5) + (y * 0.5) 169 | z = (((1. / (x + y + z)) * z) * 0.5) + (z * 0.5) 170 | 171 | x = 1.0 if x > 10. else x / 10. 172 | y = 1.0 if y > 10. else y / 10. 173 | z = 1.0 if z > 10. else z / 10. 174 | 175 | return x, y, z # type: ignore 176 | 177 | 178 | def process_audio(processor, model, audio_filepath, proc_num): 179 | # type: (Wav2Vec2Processor, Wav2Vec2ForCTC, Text, int) -> Tuple[float, torch.Tensor, torch.Tensor] 180 | 181 | speech, sample_rate = librosa.load(audio_filepath, sr=16000) 182 | input_values = processor(speech, sampling_rate=sample_rate, return_tensors="pt").input_values.cpu() 183 | duration_sec = input_values.shape[1] / sample_rate 184 | 185 | logits = model(input_values).logits 186 | 187 | predicted_ids = torch.argmax(logits, dim=-1) 188 | probabilities, ids = torch.topk(logits, k=3, dim=-1) 189 | 190 | transcription = processor.decode(predicted_ids[0]).lower() 191 | print(f"process {duration_sec:.3f}sec audio({proc_num}) file as {transcription}") 192 | 193 | return duration_sec, probabilities, ids 194 | 195 | 196 | class CurveKey(tuple): 197 | """Contains keyframe(time in seconds), keyvalue pair.""" 198 | def __init__(self, time, value): 199 | super().__init__() 200 | 201 | 202 | class AnimCurve(): 203 | """Contains CurveKeys.""" 204 | 205 | def add_key(self, key): 206 | pass 207 | 208 | 209 | def expand_tensor_to_frames(conf, processor, probabilities, ids): 210 | # type: (...) -> List[Dict[Text, float]] 211 | 212 | # animation_frames represents all frame plotted including empty move. 213 | animation_frames = [] # type: List[Dict[Text, float]] 214 | ipa_to_arpabet = load_ipa_arpabet_table(conf) 215 | 216 | for i, (top3_vals, top3_ids) in enumerate(zip(probabilities[0], ids[0])): 217 | 218 | animation_frames.append({}) 219 | 220 | if is_silence(top3_vals, top3_ids): 221 | continue 222 | 223 | x, y, z = calculate_voice_power(top3_vals, top3_ids) 224 | for index, val in zip(top3_ids, (x, y, z)): 225 | if index != 0 and val > 0.: 226 | ipa = processor.decode(index.item()).lower() 227 | arpabet = ipa_to_arpabet.get(ipa) 228 | if not arpabet: 229 | print(f"ipa not found in the config.toml table replacing _: {ipa}") 230 | # arpabet = ipa_to_arpabet.get("default", "_") 231 | ipa = "default" 232 | 233 | if ipa in animation_frames[i]: 234 | animation_frames[i][ipa] += val 235 | else: 236 | animation_frames[i][ipa] = val 237 | 238 | return animation_frames 239 | 240 | 241 | def tokenize_ipa_into_oral_morphemes(conf, frames): 242 | # type: (ConfType, List[Dict[Text, float]]) -> List[Dict[Text, float]] 243 | 244 | interpolation = conf.get("keyframe", {}).get("consecutive_viseme_frame", 3) 245 | ipa_to_arpabet = load_ipa_arpabet_table(conf) 246 | res = [] 247 | for _ in frames: 248 | res.append({}) 249 | 250 | for i, keys in enumerate(copy.deepcopy(frames)): 251 | for k, v in keys.items(): 252 | arpabets = ipa_to_arpabet.get(k, ["_"]) 253 | 254 | for j, arpabet in enumerate(arpabets): 255 | index = i + j * interpolation 256 | if index > len(res): 257 | for _ in range(len(res) - index + 1): 258 | res.append({}) 259 | 260 | res[index][arpabet] = v 261 | 262 | return res 263 | 264 | 265 | def generate_keyframes(conf, processor, duration_sec, probabilities, ids, offset): 266 | # type: (ConfType, Wav2Vec2Processor, float, torch.Tensor, torch.Tensor, float) -> Dict 267 | 268 | animation_frames = expand_tensor_to_frames(conf, processor, probabilities, ids) 269 | animation_frames = tokenize_ipa_into_oral_morphemes(conf, animation_frames) 270 | 271 | # Since only voiced keys are placed in the `animation_frames` here, 272 | # we need to specify the start and end frame before and after the key. 273 | for i, keys in enumerate(copy.deepcopy(animation_frames)): 274 | for k, _ in keys.items(): 275 | set_zero_key(conf, animation_frames, k, i) # animation_frames got side effect 276 | 277 | object_based_keys = {} 278 | for i, keys in enumerate(copy.deepcopy(animation_frames)): 279 | sec = (i / len(probabilities[0])) * duration_sec + offset 280 | for phon, val in keys.items(): 281 | 282 | if phon in object_based_keys: 283 | object_based_keys[phon].append((sec, val)) 284 | else: 285 | object_based_keys[phon] = [] 286 | object_based_keys[phon].append((sec, val)) 287 | 288 | return object_based_keys 289 | 290 | 291 | def set_zero_key(conf, animation_keys, phon, frame_index): 292 | # type: (ConfType, List[Dict[Text, float]], Text, int) -> ... 293 | """CAUTION: animation_keys is mutable and changed by calling this function.""" 294 | 295 | interpolation = conf.get("keyframe", {}).get("interpolation", 5) 296 | 297 | prev_key_index_start = max(1, frame_index - 1) 298 | prev_key_index_stop = max(1, frame_index - interpolation - 1) 299 | next_key_index_start = min(frame_index + 1, len(animation_keys)) 300 | next_key_index_stop = min(frame_index + interpolation + 1, len(animation_keys)) 301 | 302 | # ------------------------------ 303 | for frame in range(prev_key_index_start, prev_key_index_stop, -1): 304 | if phon in animation_keys[frame]: 305 | break 306 | # print("prev", frame, animation_keys[frame]) 307 | else: 308 | with contextlib.suppress(IndexError, UnboundLocalError, AttributeError): 309 | animation_keys[frame - 1][phon] = 0.0 # type: ignore # i know this dangerous... 310 | 311 | # ------------------------------ 312 | for frame in range(next_key_index_start, next_key_index_stop): 313 | if phon in animation_keys[frame]: 314 | break 315 | else: 316 | with contextlib.suppress(IndexError, UnboundLocalError, AttributeError): 317 | animation_keys[frame + 1][phon] = 0.0 # type: ignore 318 | 319 | 320 | def load_ipa_arpabet_table(conf): 321 | # type: (ConfType) -> Dict[Text, List[Text]] 322 | 323 | return conf.get("ipa_to_arpabet", {}) 324 | 325 | 326 | def async_split_audio(audio_filepath, audio_settings): 327 | files_and_offsets = audio_util.split_audio(audio_filepath, **audio_settings) 328 | return files_and_offsets 329 | 330 | 331 | def async_load_models(): 332 | processor, model = load_models() 333 | return processor, model 334 | 335 | 336 | def async_process_audio(config, processor, model, file_path, offset, proc_num): 337 | duration_sec, probabilities, ids = process_audio(processor, model, file_path, proc_num) 338 | keys = generate_keyframes(config, processor, duration_sec, probabilities, ids, offset) 339 | 340 | return keys 341 | 342 | 343 | def async_execute(): 344 | 345 | args = parse_args() 346 | config = load_config(args) 347 | 348 | audio_filepath = args.input_audiofile # type: pathlib.Path 349 | if audio_filepath.suffix != ".wav": 350 | raise Exception("input audio is not wav") 351 | fbx_path = audio_filepath.with_suffix(".fbx") 352 | 353 | audio_config = config.get("audio_settings", {}) 354 | 355 | futures = [] 356 | with concurrent.futures.ProcessPoolExecutor(2) as executor: 357 | f = executor.submit(async_split_audio, audio_filepath, audio_config) 358 | futures.append(f) 359 | 360 | f = executor.submit(async_load_models) 361 | futures.append(f) 362 | 363 | concurrent.futures.wait(futures) 364 | files_and_offsets = futures[0].result() 365 | processor, model = futures[1].result() 366 | 367 | total_keys = {} 368 | futures = [] 369 | proc_count = min(int(multiprocessing.cpu_count() / 2), len(files_and_offsets)) 370 | with concurrent.futures.ProcessPoolExecutor(proc_count) as executor: 371 | for i, (file_path, offset) in enumerate(files_and_offsets): 372 | f = executor.submit(async_process_audio, config, processor, model, file_path, offset, i) 373 | futures.append(f) 374 | 375 | concurrent.futures.wait(futures) 376 | for f in futures: 377 | keys = f.result() 378 | for key, value in keys.items(): 379 | if key in total_keys: 380 | total_keys[key].extend(value) 381 | else: 382 | total_keys[key] = value 383 | 384 | fbx_writer.write(total_keys, fbx_path) 385 | print("done!!") 386 | 387 | 388 | def execute(): 389 | 390 | args = parse_args() 391 | config = load_config(args) 392 | 393 | audio_filepath = args.input_audiofile # type: pathlib.Path 394 | # if audio_filepath.suffix != ".wav": 395 | # raise Exception("input audio is not wav") 396 | fbx_path = audio_filepath.with_suffix(".fbx") 397 | 398 | audio_config = config.get("audio_settings", {}) 399 | 400 | files_and_offsets = audio_util.split_audio(audio_filepath, **audio_config) 401 | processor, model = load_models() 402 | 403 | total_keys = {} 404 | for i, (file_path, offset) in enumerate(files_and_offsets): 405 | duration_sec, probabilities, ids = process_audio(processor, model, file_path, i) 406 | # print(f"{file_path}: duration: {duration_sec}, offset:{offset}") 407 | keys = generate_keyframes(config, processor, duration_sec, probabilities, ids, offset) 408 | 409 | for key, value in keys.items(): 410 | if key in total_keys: 411 | total_keys[key].extend(value) 412 | else: 413 | total_keys[key] = value 414 | 415 | fbx_writer.write(total_keys, fbx_path) 416 | print("done!!") 417 | 418 | 419 | if __name__ == '__main__': 420 | # print(timeit.timeit("execute()", setup="from __main__ import execute", number=2)) 421 | # print(timeit.timeit("asyncio.run(async_execute())", setup="import asyncio;from __main__ import async_execute", number=2)) 422 | 423 | if getattr(sys, 'frozen', False): 424 | # frozen 425 | execute() 426 | else: 427 | # unfrozen 428 | async_execute() 429 | # execute() 430 | # asyncio.run(execute()) 431 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10.0 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=1 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | file-ignored, 77 | suppressed-message, 78 | useless-suppression, 79 | deprecated-pragma, 80 | use-symbolic-message-instead, 81 | apply-builtin, 82 | basestring-builtin, 83 | buffer-builtin, 84 | cmp-builtin, 85 | coerce-builtin, 86 | execfile-builtin, 87 | file-builtin, 88 | long-builtin, 89 | raw_input-builtin, 90 | reduce-builtin, 91 | standarderror-builtin, 92 | unicode-builtin, 93 | xrange-builtin, 94 | coerce-method, 95 | delslice-method, 96 | getslice-method, 97 | setslice-method, 98 | no-absolute-import, 99 | old-division, 100 | dict-iter-method, 101 | dict-view-method, 102 | next-method-called, 103 | metaclass-assignment, 104 | indexing-exception, 105 | raising-string, 106 | reload-builtin, 107 | oct-method, 108 | hex-method, 109 | nonzero-method, 110 | cmp-method, 111 | input-builtin, 112 | round-builtin, 113 | intern-builtin, 114 | unichr-builtin, 115 | map-builtin-not-iterating, 116 | zip-builtin-not-iterating, 117 | range-builtin-not-iterating, 118 | filter-builtin-not-iterating, 119 | using-cmp-argument, 120 | eq-without-hash, 121 | div-method, 122 | idiv-method, 123 | rdiv-method, 124 | exception-message-attribute, 125 | invalid-str-codec, 126 | sys-max-int, 127 | bad-python3-import, 128 | deprecated-string-function, 129 | deprecated-str-translate-call, 130 | deprecated-itertools-function, 131 | deprecated-types-field, 132 | next-method-defined, 133 | dict-items-not-iterating, 134 | dict-keys-not-iterating, 135 | dict-values-not-iterating, 136 | deprecated-operator-function, 137 | deprecated-urllib-function, 138 | xreadlines-attribute, 139 | deprecated-sys-function, 140 | exception-escape, 141 | comprehension-escape, 142 | line-too-long, 143 | invalid-name, 144 | broad-except, 145 | missing-function-docstring, 146 | missing-class-docstring, 147 | assignment-from-no-return, 148 | too-few-public-methods, 149 | bad-whitespace, 150 | import-error, 151 | unused-variable, 152 | useless-object-inheritance 153 | 154 | # Enable the message, report, category or checker with the given id(s). You can 155 | # either give multiple identifier separated by comma (,) or put this option 156 | # multiple time (only on the command line, not in the configuration file where 157 | # it should appear only once). See also the "--disable" option for examples. 158 | enable=c-extension-no-member 159 | 160 | 161 | [REPORTS] 162 | 163 | # Python expression which should return a score less than or equal to 10. You 164 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 165 | # which contain the number of messages in each category, as well as 'statement' 166 | # which is the total number of statements analyzed. This score is used by the 167 | # global evaluation report (RP0004). 168 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 169 | 170 | # Template used to display messages. This is a python new-style format string 171 | # used to format the message information. See doc for all details. 172 | #msg-template= 173 | 174 | # Set the output format. Available formats are text, parseable, colorized, json 175 | # and msvs (visual studio). You can also give a reporter class, e.g. 176 | # mypackage.mymodule.MyReporterClass. 177 | output-format=text 178 | 179 | # Tells whether to display a full report or only the messages. 180 | reports=no 181 | 182 | # Activate the evaluation score. 183 | score=yes 184 | 185 | 186 | [REFACTORING] 187 | 188 | # Maximum number of nested blocks for function / method body 189 | max-nested-blocks=5 190 | 191 | # Complete name of functions that never returns. When checking for 192 | # inconsistent-return-statements if a never returning function is called then 193 | # it will be considered as an explicit return statement and no message will be 194 | # printed. 195 | never-returning-functions=sys.exit 196 | 197 | 198 | [BASIC] 199 | 200 | # Naming style matching correct argument names. 201 | argument-naming-style=snake_case 202 | 203 | # Regular expression matching correct argument names. Overrides argument- 204 | # naming-style. 205 | #argument-rgx= 206 | 207 | # Naming style matching correct attribute names. 208 | attr-naming-style=snake_case 209 | 210 | # Regular expression matching correct attribute names. Overrides attr-naming- 211 | # style. 212 | #attr-rgx= 213 | 214 | # Bad variable names which should always be refused, separated by a comma. 215 | bad-names=foo, 216 | bar, 217 | baz, 218 | toto, 219 | tutu, 220 | tata 221 | 222 | # Bad variable names regexes, separated by a comma. If names match any regex, 223 | # they will always be refused 224 | bad-names-rgxs= 225 | 226 | # Naming style matching correct class attribute names. 227 | class-attribute-naming-style=any 228 | 229 | # Regular expression matching correct class attribute names. Overrides class- 230 | # attribute-naming-style. 231 | #class-attribute-rgx= 232 | 233 | # Naming style matching correct class names. 234 | class-naming-style=PascalCase 235 | 236 | # Regular expression matching correct class names. Overrides class-naming- 237 | # style. 238 | #class-rgx= 239 | 240 | # Naming style matching correct constant names. 241 | const-naming-style=UPPER_CASE 242 | 243 | # Regular expression matching correct constant names. Overrides const-naming- 244 | # style. 245 | #const-rgx= 246 | 247 | # Minimum line length for functions/classes that require docstrings, shorter 248 | # ones are exempt. 249 | docstring-min-length=-1 250 | 251 | # Naming style matching correct function names. 252 | function-naming-style=snake_case 253 | 254 | # Regular expression matching correct function names. Overrides function- 255 | # naming-style. 256 | #function-rgx= 257 | 258 | # Good variable names which should always be accepted, separated by a comma. 259 | good-names=i, 260 | j, 261 | k, 262 | ex, 263 | Run, 264 | _ 265 | 266 | # Good variable names regexes, separated by a comma. If names match any regex, 267 | # they will always be accepted 268 | good-names-rgxs= 269 | 270 | # Include a hint for the correct naming format with invalid-name. 271 | include-naming-hint=no 272 | 273 | # Naming style matching correct inline iteration names. 274 | inlinevar-naming-style=any 275 | 276 | # Regular expression matching correct inline iteration names. Overrides 277 | # inlinevar-naming-style. 278 | #inlinevar-rgx= 279 | 280 | # Naming style matching correct method names. 281 | method-naming-style=snake_case 282 | 283 | # Regular expression matching correct method names. Overrides method-naming- 284 | # style. 285 | #method-rgx= 286 | 287 | # Naming style matching correct module names. 288 | module-naming-style=snake_case 289 | 290 | # Regular expression matching correct module names. Overrides module-naming- 291 | # style. 292 | #module-rgx= 293 | 294 | # Colon-delimited sets of names that determine each other's naming style when 295 | # the name regexes allow several styles. 296 | name-group= 297 | 298 | # Regular expression which should only match function or class names that do 299 | # not require a docstring. 300 | no-docstring-rgx=^_ 301 | 302 | # List of decorators that produce properties, such as abc.abstractproperty. Add 303 | # to this list to register other decorators that produce valid properties. 304 | # These decorators are taken in consideration only for invalid-name. 305 | property-classes=abc.abstractproperty 306 | 307 | # Naming style matching correct variable names. 308 | variable-naming-style=snake_case 309 | 310 | # Regular expression matching correct variable names. Overrides variable- 311 | # naming-style. 312 | #variable-rgx= 313 | 314 | 315 | [FORMAT] 316 | 317 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 318 | expected-line-ending-format= 319 | 320 | # Regexp for a line that is allowed to be longer than the limit. 321 | ignore-long-lines=^\s*(# )??$ 322 | 323 | # Number of spaces of indent required inside a hanging or continued line. 324 | indent-after-paren=4 325 | 326 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 327 | # tab). 328 | indent-string=' ' 329 | 330 | # Maximum number of characters on a single line. 331 | max-line-length=100 332 | 333 | # Maximum number of lines in a module. 334 | max-module-lines=1000 335 | 336 | # Allow the body of a class to be on the same line as the declaration if body 337 | # contains single statement. 338 | single-line-class-stmt=no 339 | 340 | # Allow the body of an if to be on the same line as the test if there is no 341 | # else. 342 | single-line-if-stmt=no 343 | 344 | 345 | [LOGGING] 346 | 347 | # The type of string formatting that logging methods do. `old` means using % 348 | # formatting, `new` is for `{}` formatting. 349 | logging-format-style=old 350 | 351 | # Logging modules to check that the string format arguments are in logging 352 | # function parameter format. 353 | logging-modules=logging 354 | 355 | 356 | [MISCELLANEOUS] 357 | 358 | # List of note tags to take in consideration, separated by a comma. 359 | notes=FIXME, 360 | XXX, 361 | TODO 362 | 363 | # Regular expression of note tags to take in consideration. 364 | #notes-rgx= 365 | 366 | 367 | [SIMILARITIES] 368 | 369 | # Ignore comments when computing similarities. 370 | ignore-comments=yes 371 | 372 | # Ignore docstrings when computing similarities. 373 | ignore-docstrings=yes 374 | 375 | # Ignore imports when computing similarities. 376 | ignore-imports=no 377 | 378 | # Minimum lines number of a similarity. 379 | min-similarity-lines=4 380 | 381 | 382 | [SPELLING] 383 | 384 | # Limits count of emitted suggestions for spelling mistakes. 385 | max-spelling-suggestions=4 386 | 387 | # Spelling dictionary name. Available dictionaries: none. To make it work, 388 | # install the python-enchant package. 389 | spelling-dict= 390 | 391 | # List of comma separated words that should not be checked. 392 | spelling-ignore-words= 393 | 394 | # A path to a file that contains the private dictionary; one word per line. 395 | spelling-private-dict-file= 396 | 397 | # Tells whether to store unknown words to the private dictionary (see the 398 | # --spelling-private-dict-file option) instead of raising a message. 399 | spelling-store-unknown-words=no 400 | 401 | 402 | [STRING] 403 | 404 | # This flag controls whether inconsistent-quotes generates a warning when the 405 | # character used as a quote delimiter is used inconsistently within a module. 406 | check-quote-consistency=no 407 | 408 | # This flag controls whether the implicit-str-concat should generate a warning 409 | # on implicit string concatenation in sequences defined over several lines. 410 | check-str-concat-over-line-jumps=no 411 | 412 | 413 | [TYPECHECK] 414 | 415 | # List of decorators that produce context managers, such as 416 | # contextlib.contextmanager. Add to this list to register other decorators that 417 | # produce valid context managers. 418 | contextmanager-decorators=contextlib.contextmanager 419 | 420 | # List of members which are set dynamically and missed by pylint inference 421 | # system, and so shouldn't trigger E1101 when accessed. Python regular 422 | # expressions are accepted. 423 | generated-members= 424 | 425 | # Tells whether missing members accessed in mixin class should be ignored. A 426 | # mixin class is detected if its name ends with "mixin" (case insensitive). 427 | ignore-mixin-members=yes 428 | 429 | # Tells whether to warn about missing members when the owner of the attribute 430 | # is inferred to be None. 431 | ignore-none=yes 432 | 433 | # This flag controls whether pylint should warn about no-member and similar 434 | # checks whenever an opaque object is returned when inferring. The inference 435 | # can return multiple potential results while evaluating a Python object, but 436 | # some branches might not be evaluated, which results in partial inference. In 437 | # that case, it might be useful to still emit no-member and other checks for 438 | # the rest of the inferred objects. 439 | ignore-on-opaque-inference=yes 440 | 441 | # List of class names for which member attributes should not be checked (useful 442 | # for classes with dynamically set attributes). This supports the use of 443 | # qualified names. 444 | ignored-classes=optparse.Values,thread._local,_thread._local 445 | 446 | # List of module names for which member attributes should not be checked 447 | # (useful for modules/projects where namespaces are manipulated during runtime 448 | # and thus existing member attributes cannot be deduced by static analysis). It 449 | # supports qualified module names, as well as Unix pattern matching. 450 | ignored-modules= 451 | 452 | # Show a hint with possible names when a member name was not found. The aspect 453 | # of finding the hint is based on edit distance. 454 | missing-member-hint=yes 455 | 456 | # The minimum edit distance a name should have in order to be considered a 457 | # similar match for a missing member name. 458 | missing-member-hint-distance=1 459 | 460 | # The total number of similar names that should be taken in consideration when 461 | # showing a hint for a missing member. 462 | missing-member-max-choices=1 463 | 464 | # List of decorators that change the signature of a decorated function. 465 | signature-mutators= 466 | 467 | 468 | [VARIABLES] 469 | 470 | # List of additional names supposed to be defined in builtins. Remember that 471 | # you should avoid defining new builtins when possible. 472 | additional-builtins= 473 | 474 | # Tells whether unused global variables should be treated as a violation. 475 | allow-global-unused-variables=yes 476 | 477 | # List of strings which can identify a callback function by name. A callback 478 | # name must start or end with one of those strings. 479 | callbacks=cb_, 480 | _cb 481 | 482 | # A regular expression matching the name of dummy variables (i.e. expected to 483 | # not be used). 484 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 485 | 486 | # Argument names that match this expression will be ignored. Default to name 487 | # with leading underscore. 488 | ignored-argument-names=_.*|^ignored_|^unused_ 489 | 490 | # Tells whether we should check for unused import in __init__ files. 491 | init-import=no 492 | 493 | # List of qualified module names which can have objects that can redefine 494 | # builtins. 495 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 496 | 497 | 498 | [CLASSES] 499 | 500 | # List of method names used to declare (i.e. assign) instance attributes. 501 | defining-attr-methods=__init__, 502 | __new__, 503 | setUp, 504 | __post_init__ 505 | 506 | # List of member names, which should be excluded from the protected access 507 | # warning. 508 | exclude-protected=_asdict, 509 | _fields, 510 | _replace, 511 | _source, 512 | _make 513 | 514 | # List of valid names for the first argument in a class method. 515 | valid-classmethod-first-arg=cls 516 | 517 | # List of valid names for the first argument in a metaclass class method. 518 | valid-metaclass-classmethod-first-arg=cls 519 | 520 | 521 | [DESIGN] 522 | 523 | # Maximum number of arguments for function / method. 524 | max-args=9 525 | 526 | # Maximum number of attributes for a class (see R0902). 527 | max-attributes=12 528 | 529 | # Maximum number of boolean expressions in an if statement (see R0916). 530 | max-bool-expr=5 531 | 532 | # Maximum number of branch for function / method body. 533 | max-branches=12 534 | 535 | # Maximum number of locals for function / method body. 536 | max-locals=15 537 | 538 | # Maximum number of parents for a class (see R0901). 539 | max-parents=7 540 | 541 | # Maximum number of public methods for a class (see R0904). 542 | max-public-methods=20 543 | 544 | # Maximum number of return / yield for function / method body. 545 | max-returns=6 546 | 547 | # Maximum number of statements in function / method body. 548 | max-statements=50 549 | 550 | # Minimum number of public methods for a class (see R0903). 551 | min-public-methods=2 552 | 553 | 554 | [IMPORTS] 555 | 556 | # List of modules that can be imported at any level, not just the top level 557 | # one. 558 | allow-any-import-level= 559 | 560 | # Allow wildcard imports from modules that define __all__. 561 | allow-wildcard-with-all=no 562 | 563 | # Analyse import fallback blocks. This can be used to support both Python 2 and 564 | # 3 compatible code, which means that the block might have code that exists 565 | # only in one or another interpreter, leading to false positives when analysed. 566 | analyse-fallback-blocks=no 567 | 568 | # Deprecated modules which should not be used, separated by a comma. 569 | deprecated-modules=optparse,tkinter.tix 570 | 571 | # Create a graph of external dependencies in the given file (report RP0402 must 572 | # not be disabled). 573 | ext-import-graph= 574 | 575 | # Create a graph of every (i.e. internal and external) dependencies in the 576 | # given file (report RP0402 must not be disabled). 577 | import-graph= 578 | 579 | # Create a graph of internal dependencies in the given file (report RP0402 must 580 | # not be disabled). 581 | int-import-graph= 582 | 583 | # Force import order to recognize a module as part of the standard 584 | # compatibility libraries. 585 | known-standard-library= 586 | 587 | # Force import order to recognize a module as part of a third party library. 588 | known-third-party=enchant 589 | 590 | # Couples of modules and preferred modules, separated by a comma. 591 | preferred-modules= 592 | 593 | 594 | [EXCEPTIONS] 595 | 596 | # Exceptions that will emit a warning when being caught. Defaults to 597 | # "BaseException, Exception". 598 | overgeneral-exceptions=BaseException, 599 | Exception 600 | --------------------------------------------------------------------------------