├── subtoaudio ├── __init__.py └── subtoaudio.py ├── requirements.txt ├── setup.py ├── .gitignore ├── subtitle_to_audio.ipynb ├── README.md └── LICENSE /subtoaudio/__init__.py: -------------------------------------------------------------------------------- 1 | from .subtoaudio import SubToAudio -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | TTS 2 | librosa 3 | pydub 4 | ffmpeg-python -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open("README.md", "r", encoding="utf-8") as readme_file: 4 | README = readme_file.read() 5 | 6 | setup( 7 | name="subtoaudio", 8 | packages=find_packages(exclude=[]), 9 | version="0.1.5", 10 | license="MPL 2.0", 11 | description="Subtitle to Audio, generate audio or speech from any subtitle file", 12 | author="Bagas NS", 13 | author_email="bagassantoso71@gmail.com", 14 | long_description=README, 15 | long_description_content_type="text/markdown", 16 | url="https://github.com/bnsantoso/", 17 | install_requires=["pydub","librosa","ffmpeg-python"], 18 | keywords=["subtitle", "tts", "text to audio", "subtitle to audio", "subtitle to speech",], 19 | classifiers=[ 20 | "Development Status :: 4 - Beta", 21 | "Intended Audience :: Developers", 22 | "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", 23 | "Programming Language :: Python :: 3.9", 24 | ], 25 | include_package_data=True, 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | *.ipynb 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /subtitle_to_audio.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [] 7 | }, 8 | "kernelspec": { 9 | "name": "python3", 10 | "display_name": "Python 3" 11 | }, 12 | "language_info": { 13 | "name": "python" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "source": [ 20 | "# Subtitle to Audio\n", 21 | "\n", 22 | "Convert subtitle file into speech using coqui-ai TTS\n", 23 | "\n", 24 | "* github repo : [sub to audio by bnsantoso](https://https://github.com/bnsantoso/sub-to-audio)\n", 25 | "\n", 26 | "* [buy me coffee](https://ko-fi.com/bnsantoso)\n", 27 | "\n", 28 | "##Raw Input, make sure type your data type properly.\n", 29 | "\n", 30 | "\n" 31 | ], 32 | "metadata": { 33 | "id": "79Q0eH6SbpX9" 34 | } 35 | }, 36 | { 37 | "cell_type": "code", 38 | "source": [ 39 | "#@title install\n", 40 | "from_github = True # @param {type:\"boolean\"}\n", 41 | "if from_github:\n", 42 | " !pip install git+https://github.com/bnsantoso/sub-to-audio -q\n", 43 | "else:\n", 44 | " !pip install subtoaudio -q\n", 45 | "\n", 46 | "!pip install TTS" 47 | ], 48 | "metadata": { 49 | "id": "Ykwn9DrudNbe", 50 | "cellView": "form" 51 | }, 52 | "execution_count": null, 53 | "outputs": [] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "source": [ 58 | "#@title Set Token\n", 59 | "import os\n", 60 | "\n", 61 | "token = \"\" # @param {type:\"string\"}\n", 62 | "os.environ[\"COQUI_STUDIO_TOKEN\"] = token" 63 | ], 64 | "metadata": { 65 | "cellView": "form", 66 | "id": "frpP0XVLXJGF" 67 | }, 68 | "execution_count": null, 69 | "outputs": [] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "source": [ 74 | "#@title Load model\n", 75 | "from subtoaudio import SubToAudio\n", 76 | "\n", 77 | "model_name = \"tts_models/en/multi-dataset/tortoise-v2\" #@param {type:\"raw\"}\n", 78 | "fairseq_language = None #@param {type:\"raw\"}\n", 79 | "model_path = None #@param {type:\"raw\"}\n", 80 | "config_path = None #@param {type:\"raw\"}\n", 81 | "kwargs = {} #@param {type:\"raw\"}\n", 82 | "\n", 83 | "sub = SubToAudio(model_name = model_name,\n", 84 | " model_path = model_path,\n", 85 | " config_path = config_path,\n", 86 | " fairseq_language = fairseq_language,\n", 87 | " **kwargs)\n" 88 | ], 89 | "metadata": { 90 | "cellView": "form", 91 | "id": "K3yog0v__7Aw" 92 | }, 93 | "execution_count": null, 94 | "outputs": [] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "source": [ 99 | "#@title Subtitle Data\n", 100 | "subtitle_path = \"/content/Untitled Folder/Untitled.ass\" #@param {type:\"raw\"}\n", 101 | "subtitle = sub.subtitle(subtitle_path)" 102 | ], 103 | "metadata": { 104 | "cellView": "form", 105 | "id": "o3qJQ8QZmVLY" 106 | }, 107 | "execution_count": 8, 108 | "outputs": [] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "source": [ 113 | "#@title Convert Subtitle to Audio\n", 114 | "\n", 115 | "speaker = None #@param {type:\"raw\"}\n", 116 | "speaker_wav = None #@param {type:\"raw\"}\n", 117 | "voice_dir = None #@param {type:\"raw\"}\n", 118 | "speaker_wav = None #@param {type:\"raw\"}\n", 119 | "language = \"en\" #@param {type:\"raw\"}\n", 120 | "tempo_mode = \"overflow\" #@param {type:\"raw\"}\n", 121 | "tempo_speed = None #@param {type:\"raw\"}\n", 122 | "tempo_limit = None #@param {type:\"raw\"}\n", 123 | "shift_mode = \"right\" #@param {type:\"raw\"}\n", 124 | "shift_limit = None #@param {type:\"raw\"}\n", 125 | "output_path = \"/content/Untitled Folder/Untitled.ass\" #@param {type:\"raw\"}\n", 126 | "speed = None #@param {type:\"raw\"}\n", 127 | "emotion = None #@param {type:\"raw\"}\n", 128 | "kwargs2 = {} #@param {type:\"raw\"}\n", 129 | "voice_conversion = False #@param {type:\"boolean\"}\n", 130 | "save_temp = True #@param {type:\"boolean\"}\n", 131 | "\n", 132 | "sub.convert_to_audio(sub_data=subtitle,\n", 133 | " speaker = speaker,\n", 134 | " language = language,\n", 135 | " speaker_wav = speaker_wav,\n", 136 | " voice_dir = voice_dir,\n", 137 | " tempo_mode = tempo_mode,\n", 138 | " tempo_speed = tempo_speed,\n", 139 | " tempo_limit = tempo_limit,\n", 140 | " shift_mode = shift_mode,\n", 141 | " shift_limit = shift_limit,\n", 142 | " output_path = output_path,\n", 143 | " save_temp = save_temp,\n", 144 | " voice_conversion = voice_conversion,\n", 145 | " **kwargs2,\n", 146 | " )" 147 | ], 148 | "metadata": { 149 | "cellView": "form", 150 | "id": "7lklyOINmN4H" 151 | }, 152 | "execution_count": null, 153 | "outputs": [] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "source": [ 158 | "#@title Restart Runtime if memory full\n", 159 | "\n", 160 | "import os\n", 161 | "os.kill(os.getpid(), 9)" 162 | ], 163 | "metadata": { 164 | "cellView": "form", 165 | "id": "HiDXgKJxaLfG" 166 | }, 167 | "execution_count": null, 168 | "outputs": [] 169 | } 170 | ] 171 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Subtitle to Audio 2 | Subtitle to audio, generate audio/speech from any subtitle file using Coqui-ai TTS and synchronize the audio timing according to subtitle time. 3 | 4 | **Demo :** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/bnsantoso/sub-to-audio//blob/main/subtitle_to_audio.ipynb) 5 | 6 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/bnsantoso) 7 | ## Dependencies 8 | [ffmpeg](https://ffmpeg.org/), [pydub](https://github.com/jiaaro/pydub), [librosa](https://github.com/librosa/librosa), [coqui-ai TTS](https://github.com/coqui-ai/TTS/), [ffmpeg-python](https://github.com/kkroening/ffmpeg-python) 9 | 10 | ## Installation 11 | 12 | ```bash 13 | pip install TTS 14 | pip install git+https://github.com/bnsantoso/sub-to-audio 15 | ``` 16 | ```bash 17 | pip install TTS 18 | pip install subtoaudio 19 | ``` 20 | ffmpeg on linux 21 | ```bash 22 | apt-get install ffmpeg 23 | ``` 24 | ## Example usage 25 | 26 | Basic use is very similiar to [Coqui-ai TTS](https://github.com/coqui-ai/TTS/), you can check their [documentation](https://tts.readthedocs.io/en/latest/inference.html) and the [](https://dl.fbaipublicfiles.com/mms/tts/all-tts-languages.html). 27 | 28 | **!Note: Use non-overlapping subtitles with an optimal Character per Second / CPS for best result** 29 | 30 | **!Note: Use software like aegisub to edit your subtitle** 31 | 32 | ```python 33 | from subtoaudio import SubToAudio 34 | 35 | # list all model 36 | SubToAudio().coqui_model() 37 | 38 | # get model index 39 | model = SubToAudio().coqui_model()[1] 40 | 41 | # The code will output 'yoursubtitle.wav' in the current directory. 42 | sub = SubToAudio(model_name=model) 43 | subtitle = sub.subtitle("yoursubtitle.srt") 44 | sub.convert_to_audio(sub_data=subtitle) 45 | 46 | # you can choose 1100 different language using fairseq model 47 | sub = SubToAudio(fairseq_language='') 48 | subtitle = sub.subtitle("yoursubtitle.ass") 49 | sub.convert_to_audio(sub_data=subtitle) 50 | 51 | # specify model name 52 | sub = SubToAudio(model_name="tts_models/multilingual/multi-dataset/your_tts") 53 | subtitle = sub.subtitle("yoursubtitle.srt") 54 | sub.convert_to_audio(sub_data=subtitle, output_path="subtitle.wav") 55 | 56 | # specify model and config path 57 | sub = SubToAudio(model_path="path/to/your/model.pth" config_path="config/path.json") 58 | subtitle = sub.subtitle("yoursubtitle.srt") 59 | sub.convert_to_audio(sub_data=subtitle) 60 | 61 | # speaker=tts.speakers[0] or None if model doesnt have multiple speakers 62 | # language=tts.languages[0] or None if doesnt have multiple languages 63 | 64 | # list speaker 65 | sub.speakers() 66 | speaker1 = sub.speakers()[1] 67 | 68 | # list languages 69 | sub.languages() 70 | langu = sub.languages()[0] 71 | 72 | sub = SubToAudio(model_name="tts_models/multilingual/multi-dataset/your_tts") 73 | subtitle = sub.subtitle("yoursubtitle.srt") 74 | sub.convert_to_audio(sub_data=subtitle, language=langu, speaker=speaker1, output_path="subtitle.wav") 75 | 76 | # Save temporary audio to current folder 77 | sub = SubToAudio(model_name="tts_models/multilingual/multi-dataset/your_tts") 78 | subtitle = sub.subtitle("yoursubtitle.srt") 79 | sub.convert_to_audio(sub_data=subtitle, output_path="subtitle.wav", save_temp=True) 80 | ``` 81 | 82 | ## Voice Conversion 83 | 84 | To use voice conversion method, you must pass `voice_conversion:bool` and `speaker_wav:str` paramater on `self.convert_to_audio`. Voice conversion cannot run if your model have multiple speakers. 85 | 86 | ```python 87 | from subtoaudio import SubToAudio 88 | 89 | sub = SubToAudio(fairseq_language="eng") 90 | subtitle = sub.subtitle("yoursubtitle.srt") 91 | sub.convert_to_audio(sub_data=subtitle, voice_conversion=True, speaker_wav="voice.wav", language="en") 92 | ``` 93 | 94 | ## Coqui Studio Api 95 | 96 | To use Coqui Studio Api you'll need to configure the COQUI_STUDIO_TOKEN environment variable. 97 | 98 | ```python 99 | import os 100 | 101 | os.environ['COQUI_STUDIO_TOKEN'] = # yourapi 102 | ``` 103 | 104 | After your token set you can get coqui studio model, you can follow this name convention `coqui_studio/en//coqui_studio` 105 | 106 | ```python 107 | from subtoaudio import SubToAudio 108 | 109 | sub = SubToAudio(model_name="coqui_studio/en/Torcull Diarmuid/coqui_studio", progress_bar=False) 110 | subtitle = sub.subtitle("yoursubtitle.srt") 111 | sub.convert_to_audio(sub_data=subtitle, output_path="subtitle.wav", save_temp=True) 112 | 113 | # use emotion paramater and speed paramater 114 | sub.convert_to_audio(sub_data=subtitle, output_path="subtitle.wav", emotion="Happy", speed=1.5) 115 | ``` 116 | 117 | ## Tempo Mode 118 | 119 | Use the `tempo_mode` parameter to speed up the audio. There are three tempo modes: 120 | 121 | - `tempo_mode="all"` : This accelerates all audio. Use `tempo_speed=float` to specify the speed. 122 | - `tempo_mode="overflow"` : This accelerates the audio to match the total subtitle duration plus the blank duration before the next subtitle appears. `'tempo_limit'` will limit the speed increase during overflow. 123 | - `tempo_mode="precise"` : This accelerates the audio to match the duration the subtitle appears." 124 | 125 | 126 | ```python 127 | from subtoaudio import SubToAudio 128 | 129 | # Speed up tempo or speech rate 130 | sub = SubToAudio(model_name="tts_models/de/thorsten/tacotron2-DDC") 131 | subtitle = sub.subtitle("yoursubtitle.srt") 132 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="all", tempo_speed=1.3) 133 | 134 | # Change the tempo or speech rate of all audio files , default is 1.2 135 | sub = SubToAudio("tts_models/multilingual/multi-dataset/xtts_v1") 136 | subtitle = sub.subtitle("yoursubtitle.srt") 137 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="all", tempo_speed=1.3) 138 | 139 | # Change tempo or speech rate to audio that doesn't match the subtitle duration 140 | sub = SubToAudio(fairseq_language="ind") 141 | subtitle = sub.subtitle("yoursubtitle.srt") 142 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="overflow") 143 | 144 | # Limit tempo speed on the overflow mode 145 | sub = SubToAudio(fairseq_language="ind") 146 | subtitle = sub.subtitle("yoursubtitle.srt") 147 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="overflow", tempo_limit=1.2) 148 | 149 | # Match audio length to subtitle duration 150 | sub = SubToAudio(fairseq_language="ind") 151 | subtitle = sub.subtitle("yoursubtitle.srt") 152 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="precise") 153 | ``` 154 | 155 | ## Shift Mode 156 | 157 | `shift_mode` parameter will shift audio that doesnt match subtitle duration. 158 | 159 | - `shift_mode="right"` : Shift audio time to the right and prevent audio overlaping. 160 | - `shift_mode="left"` : Shift audio to the left and prevent audio overlap, but be cautious of limited space on the left side, as some audio may disappear. 161 | - `shift_mode="interpose"` : Shift audio to mid position and prevent right and left of audio overlaping. (Note: This mode can be clunky, so use it cautiously.) 162 | - `shift_mode="left-overlap"` : Shift audio time to the left, allowing overlap. 163 | - `shift_mode="interpose-overlap"` : Shift audio to mid position, allowing overlap. 164 | - `shift_limit=int or "str"` : limit audio shift, use integer for millisecond or string like `2.5s` for second 165 | 166 | ```python 167 | from subtoaudio import SubToAudio 168 | 169 | # shift mode with limit of 2 second to the right. 170 | 171 | sub = SubToAudio(fairseq_language="vie") 172 | subtitle = sub.subtitle("yoursubtitle.srt") 173 | sub.convert_to_audio(sub_data=sub, tempo_mode="overflow", shift_mode="right", limit_shift="2s") 174 | 175 | # shift audio to left position or, time before next subtitle appear 176 | 177 | sub = SubToAudio(fairseq_language="fra") 178 | subtitle = sub.subtitle("yoursubtitle.srt") 179 | sub.convert_to_audio(sub_data=sub, shift_mode="left-overlap") 180 | 181 | # shift to left, and limit shift only 1 sec. 182 | sub = SubToAudio(fairseq_language="ind") 183 | subtitle = sub.subtitle("yoursubtitle.srt") 184 | sub.convert_to_audio(sub_data=sub, shift_mode="left", shift_limit=1000) # 1000 = 1s 185 | ``` 186 | 187 | ## Bark and Tortoise example 188 | 189 | ```python 190 | from subtoaudio import SubToAudio 191 | 192 | # Random Speaker will give you weird result when using bark model with SubToAudio 193 | 194 | # Bark random 195 | sub = SubToAudio("tts_models/multilingual/multi-dataset/bark") 196 | subtitle = sub.subtitle("yoursubtitle.srt") 197 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="overflow") 198 | 199 | # Tortoise random 200 | sub = SubToAudio("tts_models/en/multi-dataset/tortoise-v2") 201 | subtitle = sub.subtitle("yoursubtitle.srt") 202 | sub.convert_to_audio(sub_data=subtitle, shift_mode="overflow", preset="fast") 203 | 204 | # To use voice clone you need voice_dir and speaker paramater 205 | # Voice Clone expecting .wav or .npz file inside folder speaker_1 206 | # voice/speaker_1/hana.wav or voice/speaker_1/hana.npz 207 | # if your speaker folder only have .wav file, it will generate .npz file after you runing it. 208 | 209 | sub = SubToAudio("tts_models/multilingual/multi-dataset/bark") 210 | subtitle = sub.subtitle("yoursubtitle.srt") 211 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="overflow", voice_dir="voice/",speaker="speaker_1") 212 | 213 | # same with bark, the folder structure like this 'voice/speaker2/ron.wav' 214 | sub = SubToAudio("tts_models/en/multi-dataset/tortoise-v2") 215 | subtitle = sub.subtitle("yoursubtitle.ass") 216 | sub.convert_to_audio(sub_data=subtitle, tempo_mode="overflow", voice_dir="voice/", speaker="speaker2") 217 | 218 | ``` 219 | 220 | ### Citation 221 | Eren, G., & The Coqui TTS Team. (2021). Coqui TTS (Version 1.4) [Computer software]. https://doi.org/10.5281/zenodo.6334862 222 | 223 | -------------------------------------------------------------------------------- /subtoaudio/subtoaudio.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import copy 4 | import shutil 5 | import ffmpeg 6 | import torch 7 | import librosa 8 | import tempfile 9 | from TTS.api import TTS 10 | from pydub import AudioSegment 11 | 12 | class SubToAudio: 13 | 14 | def __init__( self, 15 | model_name:str=None, 16 | model_path:str=None, 17 | config_path:str=None, 18 | progress_bar:bool=False, 19 | fairseq_language:str=None, 20 | **kwargs, 21 | ): 22 | 23 | device = "cuda" if torch.cuda.is_available() else "cpu" 24 | 25 | if fairseq_language != None and model_name == None: 26 | model_name = f"tts_models/{fairseq_language}/fairseq/vits" 27 | if model_name != None and model_path == None: 28 | try: 29 | self.apitts = TTS(model_name=model_name, progress_bar=progress_bar, **kwargs).to(device) 30 | except: 31 | self.apitts = TTS(model_name, progress_bar=progress_bar, **kwargs).to(device) 32 | elif model_path != None and model_name == None: 33 | if config_path == None: 34 | print("Expecting config_path.json") 35 | else: 36 | self.apitts = TTS(model_path=model_path, 37 | config_path=config_path, 38 | progress_bar=progress_bar, 39 | **kwargs).to(device) 40 | 41 | def subtitle(self, file_path:str) -> list: 42 | self.name_path = file_path 43 | with tempfile.NamedTemporaryFile(suffix=".srt", delete=False) as temp_file: 44 | temp_filename = temp_file.name 45 | input_stream = ffmpeg.input(file_path) 46 | try: 47 | ffmpeg.output(input_stream, temp_filename, y='-y').run() 48 | except: 49 | temp_file.close() 50 | os.unlink(temp_filename) 51 | temp_file.close() 52 | dictionary = self._extract_data_srt(temp_filename) 53 | os.unlink(temp_filename) 54 | return dictionary 55 | 56 | def convert_to_audio(self, 57 | sub_data:list=None, 58 | speaker:str=None, 59 | language:str=None, 60 | voice_conversion:bool=False, 61 | speaker_wav:str=None, 62 | voice_dir:str=None, 63 | output_path:str=None, 64 | tempo_mode:str=None, 65 | tempo_speed:float=None, 66 | tempo_limit:float=None, 67 | shift_mode:str=None, 68 | shift_limit:int or str=None, 69 | save_temp:bool=False, 70 | speed:float=None, 71 | emotion:str=None, 72 | **kwargs, 73 | ): 74 | 75 | shift_set = {"right", "left", "interpose", "left-overlap", "interpose-overlap"} 76 | data = copy.deepcopy(sub_data) 77 | convert_param = {} 78 | common_param = {"language":language, 79 | "speaker_wav":speaker_wav 80 | } 81 | vcfalse_param = { "voice_dir":voice_dir, 82 | "emotion":emotion, 83 | "speed":speed, 84 | "speaker":speaker, 85 | } 86 | 87 | try: 88 | if speaker == None: 89 | vcfalse_param['speaker'] = self.apitts.speakers[0] 90 | print(f"speaker is None, using '{vcfalse_param['speaker']}' as default") 91 | if language == None: 92 | common_param['language'] = self.apitts.languages[0] 93 | print(f"Language is None, using '{common_param['language']}' as default") 94 | except: 95 | pass 96 | 97 | if output_path == None: 98 | output_path = os.path.splitext(self.name_path)[0] + ".wav" 99 | elif os.path.splitext(output_path)[1] != ".wav": 100 | output_path += ".wav" 101 | 102 | if tempo_mode == "all": 103 | if tempo_speed is None or not isinstance(tempo_speed, float): 104 | tempo_speed = 1.2 105 | print(f"tempo_speed speed is not Float") 106 | print(f"tempo_speed change to default value '{tempo_speed}'") 107 | 108 | if voice_conversion: 109 | convert_param = {**common_param} 110 | tts_method = self.apitts.tts_with_vc_to_file 111 | else: 112 | convert_param = {**common_param,**vcfalse_param} 113 | tts_method = self.apitts.tts_to_file 114 | 115 | with tempfile.TemporaryDirectory() as temp_folder: 116 | print("Temporary folder:", temp_folder) 117 | 118 | for entry_data in data: 119 | audio_path = f"{temp_folder}/{entry_data['audio_name']}" 120 | tts_method(f"{entry_data['text']}",file_path=audio_path,**convert_param,**kwargs) 121 | 122 | if tempo_mode == "all": 123 | self._tempo(mode=tempo_mode,audiopath=audio_path, 124 | tempospeed = tempo_speed) 125 | 126 | elif tempo_mode == "overflow" or tempo_mode == "precise": 127 | audio_length = self._audio_length(audio_path) 128 | subt_time = entry_data['sub_time'] 129 | if audio_length > subt_time: 130 | if tempo_mode == "overflow": 131 | sub_time = subt_time 132 | elif tempo_mode == "precise": 133 | sub_time = entry_data['end_time'] - entry_data['start_time'] 134 | shift_mode = None 135 | self._tempo(mode=tempo_mode, 136 | audiopath = audio_path, 137 | audiolength=audio_length, 138 | subtime=sub_time, 139 | tempolimit=tempo_limit) 140 | 141 | audio_length = self._audio_length(audio_path) 142 | entry_data['audio_length'] = audio_length 143 | 144 | if shift_mode in shift_set: 145 | try: 146 | if shift_limit[-1] == "s": 147 | shift_limit = int(float(shift_limit[:-1]) * 1000) 148 | except: 149 | shift_limit = None 150 | data = self._shifter(data=data, mode=shift_mode, shiftlimit=shift_limit) 151 | 152 | end_time = data[-1]['end_time'] 153 | base_duration = end_time + 10000 154 | blank_base_audio = AudioSegment.silent(duration=base_duration) 155 | 156 | for entry_data in data: 157 | audio_path = f"{temp_folder}/{entry_data['audio_name']}" 158 | position = entry_data['start_time'] 159 | overlay_audio = AudioSegment.from_file(audio_path) 160 | blank_base_audio = blank_base_audio.overlay(overlay_audio, position=position) 161 | 162 | blank_base_audio.export(output_path, format="wav") 163 | 164 | if save_temp: 165 | new_folder_name = f"{os.path.splitext(self.name_path)[0]}_{os.path.basename(os.path.normpath(temp_folder))}" 166 | self._move_tempaudio(temp_folder, new_folder_name) 167 | 168 | def _tempo(self, 169 | mode:str, 170 | audiopath:str, 171 | tempospeed:float=None, 172 | audiolength:int=None, 173 | subtime:int=None, 174 | tempolimit:float=None, 175 | ): 176 | 177 | if mode == "all": 178 | atempo = tempospeed 179 | if mode == "overflow": 180 | atempo = audiolength / subtime 181 | if tempolimit is not None and isinstance(tempolimit, float): 182 | if atempo > tempolimit: 183 | atempo = tempolimit 184 | if mode == "precise": 185 | atempo = audiolength / subtime 186 | 187 | atempo = round(atempo, 2) 188 | audio_out = audiopath+"temp.wav" 189 | print(f" > atempo: {atempo}") 190 | ffmpeg_command = (ffmpeg.input(audiopath).filter('atempo',atempo).output(audio_out)) 191 | try: 192 | ffmpeg_command.run(capture_stdout=True, capture_stderr=True) 193 | except ffmpeg.Error as e: 194 | print('stdout:', e.stdout.decode('utf8')) 195 | print('stderr:', e.stderr.decode('utf8')) 196 | raise e 197 | os.rename(audiopath, audiopath + "original.wav") 198 | os.rename(audio_out, audiopath) 199 | 200 | def _audio_length(self, audio_path) -> int: 201 | return int(round(librosa.get_duration(path=audio_path),3) * 1000) 202 | 203 | def _shifter(self, data:list, mode:str, shiftlimit:int=None) -> list: 204 | 205 | if mode == "right": 206 | for i in range(len(data)): 207 | if data[i]['audio_length'] > data[i]['sub_time']: 208 | shift_time = data[i]['audio_length'] - data[i]['sub_time'] 209 | if isinstance(shiftlimit, int) and shiftlimit < shift_time: 210 | shift_time = shiftlimit 211 | if i + 1 < len(data): 212 | data[i+1]['start_time'] += shift_time 213 | data[i+1]['end_time'] += shift_time 214 | data[i+1]['sub_time'] -= shift_time 215 | 216 | elif "left" in mode or mode == "left": 217 | data = data[::-1] 218 | for i in range(len(data)): 219 | if data[i]['audio_length'] > data[i]['sub_time']: 220 | shift_time = data[i]['audio_length'] - data[i]['sub_time'] 221 | if isinstance(shiftlimit, int) and shiftlimit < shift_time: 222 | shift_time = shiftlimit 223 | data[i]['start_time'] -= shift_time 224 | data[i]['end_time'] -= shift_time 225 | if "-overlap" not in mode: 226 | if i + 1 < len(data): 227 | data[i+1]['sub_time'] -= shift_time 228 | data = data[::-1] 229 | 230 | elif "interpose" in mode or mode == "interpose": 231 | for i in range(len(data)): 232 | if data[i]['audio_length'] > data[i]['sub_time']: 233 | shift_time = int((data[i]['audio_length'] - data[i]['sub_time']) / 2) 234 | data[i]['start_time'] -= shift_time 235 | data[i]['end_time'] -= shift_time 236 | if "-overlap" not in mode: 237 | if i + 1 < len(data): 238 | data[i+1]['start_time'] += shift_time 239 | data[i+1]['end_time'] += shift_time 240 | data[i+1]['sub_time'] -= shift_time 241 | if i - 1 > 0: 242 | data[i-1]['sub_time'] -= shift_time 243 | if data[i-1]['audio_length'] > data[i-1]['sub_time']: 244 | data[i-1]['start_time'] -= shift_time 245 | data[i-1]['end_time'] -= shift_time 246 | return data 247 | 248 | def _extract_data_srt(self, file_path) -> list: 249 | subtitle_data = [] 250 | pattern = r'(\d+)\n([\d:,]+) --> ([\d:,]+)\n(.+?)\n\n' 251 | 252 | with open(file_path, 'r', encoding="utf-8") as file: 253 | file_content = file.read() 254 | 255 | matches = re.findall(pattern, file_content, re.DOTALL) 256 | 257 | for i, match in enumerate(matches): 258 | entry_number = int(match[0]) 259 | start_time = match[1] 260 | end_time = match[2] 261 | text = match[3].strip() 262 | clean_text = re.sub(r'<.*?>', '', text) 263 | start_time = self._convert_time_to_intmil(start_time) 264 | end_time = self._convert_time_to_intmil(end_time) 265 | if i < len(matches) - 1: 266 | next_start_time = self._convert_time_to_intmil(matches[i + 1][1]) 267 | sub_time = next_start_time - start_time 268 | else: 269 | sub_time = end_time - start_time + 5000 270 | sub_data = { 271 | 'entry_number': entry_number, 272 | 'start_time': start_time, 273 | 'end_time': end_time, 274 | 'text': clean_text, 275 | 'sub_time': sub_time, 276 | 'audio_name': f"{entry_number}_audio.wav" 277 | } 278 | subtitle_data.append(sub_data) 279 | file.close() 280 | return subtitle_data 281 | 282 | def _convert_time_to_intmil(self, time) -> int: 283 | time_string = time 284 | time_string = time_string.replace(":", "").replace(",", "") 285 | hours = int(time_string[:2]) 286 | minutes = int(time_string[2:4]) 287 | seconds = int(time_string[4:6]) 288 | milliseconds = int(time_string[6:]) 289 | total_milliseconds = (hours * 60 * 60 * 1000) + (minutes * 60 * 1000) + (seconds * 1000) + milliseconds 290 | return total_milliseconds 291 | 292 | def _move_tempaudio(self, folder_path, new_folder): 293 | try: 294 | new_folder_path = os.path.join(os.getcwd(), new_folder) 295 | os.mkdir(new_folder_path) 296 | if not os.path.exists(new_folder_path): 297 | os.mkdir(new_folder_path) 298 | for item in os.listdir(folder_path): 299 | item_path = os.path.join(folder_path, item) 300 | if os.path.isfile(item_path): 301 | shutil.move(item_path, new_folder) 302 | print("Temp files moved successfully!") 303 | except Exception as e: 304 | print(f"Error occurred: {e}") 305 | 306 | def speakers(self) -> list: 307 | return self.apitts.speakers 308 | 309 | def languages(self) -> list: 310 | return self.apitts.languages 311 | 312 | def coqui_model(self) -> list: 313 | return TTS().list_models() 314 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------