Full Code of rany2/edge-tts for AI

master b45c019b9948 cached
43 files
345.6 KB
86.5k tokens
85 symbols
1 requests
Download .txt
Showing preview only (361K chars total). Download the full file or copy to clipboard to get everything.
Repository: rany2/edge-tts
Branch: master
Commit: b45c019b9948
Files: 43
Total size: 345.6 KB

Directory structure:
gitextract_lm04wmz4/

├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── code-quality.yml
│       └── codeql-analysis.yml
├── .gitignore
├── .isort.cfg
├── LICENSE
├── README.md
├── build_and_publish.sh
├── examples/
│   ├── async_audio_gen_with_dynamic_voice_selection.py
│   ├── async_audio_gen_with_predefined_voice.py
│   ├── async_audio_streaming_with_predefined_voice_and_subtitles.py
│   ├── sync_audio_gen_with_predefined_voice.py
│   ├── sync_audio_streaming_with_predefined_voice_subtitles.py
│   └── sync_audio_streaming_with_predefined_voice_subtitles_print2stdout.py
├── format.sh
├── gpl-3.0.txt
├── lint.sh
├── mypy.ini
├── pylintrc
├── setup.cfg
├── setup.py
├── src/
│   ├── edge_playback/
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   ├── py.typed
│   │   ├── util.py
│   │   └── win32_playback.py
│   └── edge_tts/
│       ├── __init__.py
│       ├── __main__.py
│       ├── communicate.py
│       ├── constants.py
│       ├── data_classes.py
│       ├── drm.py
│       ├── exceptions.py
│       ├── py.typed
│       ├── srt_composer.py
│       ├── submaker.py
│       ├── typing.py
│       ├── util.py
│       ├── version.py
│       └── voices.py
└── tests/
    ├── 001-long-text.sh
    └── 001-long-text.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: rany2


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"


================================================
FILE: .github/workflows/code-quality.yml
================================================
name: "Check code quality"

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

jobs:
  lint:
    runs-on: ubuntu-latest
    name: "Lint"
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-python@v2
      with:
        python-version: 3.x
    - name: Install all dependencies
      run: pip install .[dev]
    - name: Run mypy
      run: mypy --pretty src examples
    - name: Run pylint
      run: pylint examples src
    - name: Run isort
      run: isort --check-only --diff .
    - name: Run black
      run: black --check --diff .


================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
name: "CodeQL"

on:
  push:
    branches: [ "master" ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ "master" ]
  schedule:
    - cron: '31 0 * * 1'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'python' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@v3

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        
        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality

        
    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

    #   If the Autobuild fails above, remove it and uncomment the following three lines. 
    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

    # - run: |
    #   echo "Run, Build Application using script"
    #   ./location_of_script_within_repo/buildscript.sh

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Edge-TTS specific ignores
*.mp3
*.srt
/.idea/


================================================
FILE: .isort.cfg
================================================
[settings]
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88


================================================
FILE: LICENSE
================================================
The MIT license is used for 'src/edge_tts/srt_composer.py' only. All
remaining files are licensed under the LGPLv3.

-----------------------------------------------------------------------

The MIT License

Copyright (c) 2014-2023 Christopher Down
Copyright (c) 2025- rany <rany@riseup.net>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

-----------------------------------------------------------------------

                   GNU LESSER GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.


  This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

  0. Additional Definitions.

  As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

  "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

  An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

  A "Combined Work" is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

  The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

  The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

  1. Exception to Section 3 of the GNU GPL.

  You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

  2. Conveying Modified Versions.

  If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

   a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or

   b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.

  3. Object Code Incorporating Material from Library Header Files.

  The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

   a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the object code with a copy of the GNU GPL and this license
   document.

  4. Combined Works.

  You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

   a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.

   c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.

   d) Do one of the following:

       0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.

       1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.

   e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)

  5. Combined Libraries.

  You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

   a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.

   b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.

  6. Revised Versions of the GNU Lesser General Public License.

  The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

  Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

  If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.


================================================
FILE: README.md
================================================
# edge-tts

`edge-tts` is a Python module that allows you to use Microsoft Edge's online text-to-speech service from within your Python code or using the provided `edge-tts` or `edge-playback` command.

## Installation

To install it, run the following command:

    $ pip install edge-tts

If you only want to use the `edge-tts` and `edge-playback` commands, it would be better to use `pipx`:

    $ pipx install edge-tts

## Usage

### Basic usage

If you want to use the `edge-tts` command, you can simply run it with the following command:

    $ edge-tts --text "Hello, world!" --write-media hello.mp3 --write-subtitles hello.srt

If you wish to play it back immediately with subtitles, you could use the `edge-playback` command:

    $ edge-playback --text "Hello, world!"

Note that `edge-playback` requires the installation of the [`mpv` command line player](https://mpv.io/), except on Windows.

All `edge-tts` commands work with `edge-playback` with the exception of the `--write-media`, `--write-subtitles` and `--list-voices` options.

### Changing the voice

You can change the voice used by the text-to-speech service by using the `--voice` option. The `--list-voices` option can be used to list all available voices.

    $ edge-tts --list-voices
    Name                               Gender    ContentCategories      VoicePersonalities
    ---------------------------------  --------  ---------------------  --------------------------------------
    af-ZA-AdriNeural                   Female    General                Friendly, Positive
    af-ZA-WillemNeural                 Male      General                Friendly, Positive
    am-ET-AmehaNeural                  Male      General                Friendly, Positive
    am-ET-MekdesNeural                 Female    General                Friendly, Positive
    ar-AE-FatimaNeural                 Female    General                Friendly, Positive
    ar-AE-HamdanNeural                 Male      General                Friendly, Positive
    ar-BH-AliNeural                    Male      General                Friendly, Positive
    ar-BH-LailaNeural                  Female    General                Friendly, Positive
    ar-DZ-AminaNeural                  Female    General                Friendly, Positive
    ar-DZ-IsmaelNeural                 Male      General                Friendly, Positive
    ar-EG-SalmaNeural                  Female    General                Friendly, Positive
    ...

    $ edge-tts --voice ar-EG-SalmaNeural --text "مرحبا كيف حالك؟" --write-media hello_in_arabic.mp3 --write-subtitles hello_in_arabic.srt

### Custom SSML

Support for custom SSML was removed because Microsoft prevents the use of any SSML that could not be generated by Microsoft Edge itself. This means that all the cases where custom SSML would be useful cannot be supported as the service only permits a single `<voice>` tag with a single `<prosody>` tag inside it. Any available customization options that could be used in the `<prosody>` tag are already available from the library or the command line itself.

### Changing rate, volume and pitch

You can change the rate, volume and pitch of the generated speech by using the `--rate`, `--volume` and `--pitch` options. When using a negative value, you will need to use `--[option]=-50%` instead of `--[option] -50%` to avoid the option being interpreted as a command line option.

    $ edge-tts --rate=-50% --text "Hello, world!" --write-media hello_with_rate_lowered.mp3 --write-subtitles hello_with_rate_lowered.srt
    $ edge-tts --volume=-50% --text "Hello, world!" --write-media hello_with_volume_lowered.mp3 --write-subtitles hello_with_volume_lowered.srt
    $ edge-tts --pitch=-50Hz --text "Hello, world!" --write-media hello_with_pitch_lowered.mp3 --write-subtitles hello_with_pitch_lowered.srt

## Python module

It is possible to use the `edge-tts` module directly from Python. Examples from the project itself include:

* [/examples/](/examples/)
* [/src/edge_tts/util.py](/src/edge_tts/util.py)

Other projects that use the `edge-tts` module include:

* [hass-edge-tts](https://github.com/hasscc/hass-edge-tts/blob/main/custom_components/edge_tts/tts.py)
* [Podcastfy](https://github.com/souzatharsis/podcastfy/blob/main/podcastfy/tts/providers/edge.py)
* [tts-samples](https://github.com/yaph/tts-samples/blob/main/bin/create_sound_samples.py) - a collection of [mp3 sound samples](https://github.com/yaph/tts-samples/tree/main/mp3) to facilitate picking a voice for your project.


================================================
FILE: build_and_publish.sh
================================================
#!/bin/sh
set -eux
rm -rf build dist src/*.egg-info
python3 setup.py sdist bdist_wheel
twine check dist/*
twine upload dist/* --verbose


================================================
FILE: examples/async_audio_gen_with_dynamic_voice_selection.py
================================================
#!/usr/bin/env python3

"""Simple example to generate an audio file with randomized
dynamic voice selection based on attributes such as Gender,
Language, or Locale."""

import asyncio
import random

import edge_tts
from edge_tts import VoicesManager

TEXT = "Hoy es un buen día."
OUTPUT_FILE = "spanish.mp3"


async def amain() -> None:
    """Main function"""
    voices = await VoicesManager.create()
    voice = voices.find(Gender="Male", Language="es")
    # Also supports Locales
    # voice = voices.find(Gender="Female", Locale="es-AR")

    communicate = edge_tts.Communicate(TEXT, random.choice(voice)["Name"])
    await communicate.save(OUTPUT_FILE)


if __name__ == "__main__":
    asyncio.run(amain())


================================================
FILE: examples/async_audio_gen_with_predefined_voice.py
================================================
#!/usr/bin/env python3

"""Simple example to generate audio with preset voice using async/await"""

import asyncio

import edge_tts

TEXT = "Hello World!"
VOICE = "en-GB-SoniaNeural"
OUTPUT_FILE = "test.mp3"


async def amain() -> None:
    """Main function"""
    communicate = edge_tts.Communicate(TEXT, VOICE)
    await communicate.save(OUTPUT_FILE)


if __name__ == "__main__":
    asyncio.run(amain())


================================================
FILE: examples/async_audio_streaming_with_predefined_voice_and_subtitles.py
================================================
#!/usr/bin/env python3

"""Example showing how to use use .stream() method to get audio chunks
and feed them to SubMaker to generate subtitles"""

import asyncio

import edge_tts

TEXT = "Hello World!"
VOICE = "en-GB-SoniaNeural"
OUTPUT_FILE = "test.mp3"
SRT_FILE = "test.srt"


async def amain() -> None:
    """Main function"""
    communicate = edge_tts.Communicate(TEXT, VOICE)
    submaker = edge_tts.SubMaker()
    with open(OUTPUT_FILE, "wb") as file:
        async for chunk in communicate.stream():
            if chunk["type"] == "audio":
                file.write(chunk["data"])
            elif chunk["type"] in ("WordBoundary", "SentenceBoundary"):
                submaker.feed(chunk)

    with open(SRT_FILE, "w", encoding="utf-8") as file:
        file.write(submaker.get_srt())


if __name__ == "__main__":
    asyncio.run(amain())


================================================
FILE: examples/sync_audio_gen_with_predefined_voice.py
================================================
#!/usr/bin/env python3

"""Sync variant of the example for generating audio with a predefined voice"""


import edge_tts

TEXT = "Hello World!"
VOICE = "en-GB-SoniaNeural"
OUTPUT_FILE = "test.mp3"


def main() -> None:
    """Main function"""
    communicate = edge_tts.Communicate(TEXT, VOICE)
    communicate.save_sync(OUTPUT_FILE)


if __name__ == "__main__":
    main()


================================================
FILE: examples/sync_audio_streaming_with_predefined_voice_subtitles.py
================================================
#!/usr/bin/env python3

"""Sync variant of the async .stream() method to
get audio chunks and feed them to SubMaker to
generate subtitles"""

import edge_tts

TEXT = "Hello World!"
VOICE = "en-GB-SoniaNeural"
OUTPUT_FILE = "test.mp3"
SRT_FILE = "test.srt"


def main() -> None:
    """Main function"""
    communicate = edge_tts.Communicate(TEXT, VOICE)
    submaker = edge_tts.SubMaker()
    with open(OUTPUT_FILE, "wb") as file:
        for chunk in communicate.stream_sync():
            if chunk["type"] == "audio":
                file.write(chunk["data"])
            elif chunk["type"] in ("WordBoundary", "SentenceBoundary"):
                submaker.feed(chunk)

    with open(SRT_FILE, "w", encoding="utf-8") as file:
        file.write(submaker.get_srt())


if __name__ == "__main__":
    main()


================================================
FILE: examples/sync_audio_streaming_with_predefined_voice_subtitles_print2stdout.py
================================================
#!/usr/bin/env python3

"""Sync variant of the async .stream() method to
get audio chunks and feed them to SubMaker to
generate subtitles"""
import sys

import edge_tts

TEXT = """君不见,黄河之水天上来,奔流到海不复回。
君不见,高堂明镜悲白发,朝如青丝暮成雪。
人生得意须尽欢,莫使金樽空对月。
天生我材必有用,千金散尽还复来。
烹羊宰牛且为乐,会须一饮三百杯。
岑夫子,丹丘生,将进酒,杯莫停。
与君歌一曲,请君为我倾耳听。
钟鼓馔玉不足贵,但愿长醉不复醒。
古来圣贤皆寂寞,惟有饮者留其名。
陈王昔时宴平乐,斗酒十千恣欢谑。
主人何为言少钱,径须沽取对君酌。
五花马,千金裘,呼儿将出换美酒,与尔同销万古愁。"""
VOICE = "zh-CN-YunjianNeural"


def main() -> None:
    """Main function"""
    communicate = edge_tts.Communicate(TEXT, VOICE, boundary="SentenceBoundary")
    submaker = edge_tts.SubMaker()
    stdout = sys.stdout
    audio_bytes = []
    for chunk in communicate.stream_sync():
        if chunk["type"] == "audio":
            audio_bytes.append(chunk["data"])
        elif chunk["type"] in ("WordBoundary", "SentenceBoundary"):
            submaker.feed(chunk)

    stdout.write(f"audio file length: {len(audio_bytes)}")
    stdout.write(submaker.get_srt())


if __name__ == "__main__":
    main()


================================================
FILE: format.sh
================================================
set -eux
find src examples -name '*.py' | xargs black
find src examples -name '*.py' | xargs isort


================================================
FILE: gpl-3.0.txt
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: lint.sh
================================================
#!/bin/sh
set -ux
find src examples -name '*.py' | xargs pylint
find src examples -name '*.py' | xargs mypy


================================================
FILE: mypy.ini
================================================
[mypy]
disallow_any_unimported = True
disallow_any_expr = False
disallow_any_decorated = True
disallow_any_explicit = False
disallow_any_generics = True
disallow_subclassing_any = True

disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True

warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = True
warn_return_any = True
warn_unreachable = True

strict_equality = True
strict = True


================================================
FILE: pylintrc
================================================
[MAIN]

# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no

# Clear in-memory caches upon conclusion of linting. Useful if running pylint
# in a server-like mode.
clear-cache-post-run=no

# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=

# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=

# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=

# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=

# Specify a score threshold under which the program will exit with error.
fail-under=10

# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=

# Files or directories to be skipped. They should be base names, not paths.
ignore=CVS

# Add files or directories matching the regular expressions patterns to the
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\\' represents the directory delimiter on Windows systems,
# it can't be used as an escape character.
ignore-paths=

# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores
# Emacs file locks
ignore-patterns=^\.#

# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=

# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=

# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=1

# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100

# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=

# Pickle collected data for later comparisons.
persistent=yes

# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.11

# Discover python modules and packages in the file system subtree.
recursive=no

# Add paths to the list of the source roots. Supports globbing patterns. The
# source root is an absolute path or a path relative to the current working
# directory used to determine a package namespace for modules located under the
# source root.
source-roots=

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no

# In verbose mode, extra non-checker-related info will be displayed.
#verbose=


[BASIC]

# Naming style matching correct argument names.
argument-naming-style=snake_case

# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=

# Naming style matching correct attribute names.
attr-naming-style=snake_case

# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=

# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
          bar,
          baz,
          toto,
          tutu,
          tata

# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=

# Naming style matching correct class attribute names.
class-attribute-naming-style=any

# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=

# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE

# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=

# Naming style matching correct class names.
class-naming-style=PascalCase

# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=

# Naming style matching correct constant names.
const-naming-style=UPPER_CASE

# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=

# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1

# Naming style matching correct function names.
function-naming-style=snake_case

# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
#function-rgx=

# Good variable names which should always be accepted, separated by a comma.
good-names=i,
           j,
           k,
           ex,
           Run,
           _

# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=

# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no

# Naming style matching correct inline iteration names.
inlinevar-naming-style=any

# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=

# Naming style matching correct method names.
method-naming-style=snake_case

# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
#method-rgx=

# Naming style matching correct module names.
module-naming-style=snake_case

# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=

# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=

# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_

# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty

# Regular expression matching correct type alias names. If left empty, type
# alias names will be checked with the set naming style.
#typealias-rgx=

# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=

# Naming style matching correct variable names.
variable-naming-style=snake_case

# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
#variable-rgx=


[CLASSES]

# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no

# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
                      __new__,
                      setUp,
                      asyncSetUp,
                      __post_init__

# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit

# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls

# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs


[DESIGN]

# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=

# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=

# Maximum number of arguments for function / method.
max-args=10

# Maximum number of attributes for a class (see R0902).
max-attributes=7

# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5

# Maximum number of branch for function / method body.
max-branches=20

# Maximum number of locals for function / method body.
max-locals=20

# Maximum number of parents for a class (see R0901).
max-parents=7

# Maximum number of public methods for a class (see R0904).
max-public-methods=20

# Maximum number of return / yield for function / method body.
max-returns=6

# Maximum number of statements in function / method body.
max-statements=50

# Minimum number of public methods for a class (see R0903).
min-public-methods=2


[EXCEPTIONS]

# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,builtins.Exception


[FORMAT]

# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=

# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$

# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4

# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
# tab).
indent-string='    '

# Maximum number of characters on a single line.
max-line-length=100

# Maximum number of lines in a module.
max-module-lines=1000

# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no

# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no


[IMPORTS]

# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=

# Allow explicit reexports by alias from a package __init__.
allow-reexport-from-package=no

# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no

# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=

# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=

# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=

# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=

# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=

# Force import order to recognize a module as part of a third party library.
known-third-party=enchant

# Couples of modules and preferred modules, separated by a comma.
preferred-modules=


[LOGGING]

# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old

# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging


[MESSAGES CONTROL]

# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
           CONTROL_FLOW,
           INFERENCE,
           INFERENCE_FAILURE,
           UNDEFINED

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=raw-checker-failed,
        bad-inline-option,
        locally-disabled,
        file-ignored,
        suppressed-message,
        useless-suppression,
        deprecated-pragma,
        use-symbolic-message-instead,
        use-implicit-booleaness-not-comparison-to-string,
        use-implicit-booleaness-not-comparison-to-zero,
        duplicate-code,
        consider-using-with

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=


[METHOD_ARGS]

# List of qualified names (i.e., library.method) which require a timeout
# parameter e.g. 'requests.api.get,requests.api.post'
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request


[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
      XXX,
      TODO

# Regular expression of note tags to take in consideration.
notes-rgx=


[REFACTORING]

# Maximum number of nested blocks for function / method body
max-nested-blocks=5

# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error


[REPORTS]

# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))

# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=

# Set the output format. Available formats are: text, parseable, colorized,
# json2 (improved json format), json (old json format) and msvs (visual
# studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
#output-format=

# Tells whether to display a full report or only the messages.
reports=no

# Activate the evaluation score.
score=yes


[SIMILARITIES]

# Comments are removed from the similarity computation
ignore-comments=yes

# Docstrings are removed from the similarity computation
ignore-docstrings=yes

# Imports are removed from the similarity computation
ignore-imports=yes

# Signatures are removed from the similarity computation
ignore-signatures=yes

# Minimum lines number of a similarity.
min-similarity-lines=4


[SPELLING]

# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4

# Spelling dictionary name. No available dictionaries : You need to install
# both the python package and the system dependency for enchant to work.
spelling-dict=

# List of comma separated words that should be considered directives if they
# appear at the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:

# List of comma separated words that should not be checked.
spelling-ignore-words=

# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=

# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no


[STRING]

# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no

# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no


[TYPECHECK]

# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager

# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=

# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes

# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes

# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
                          not-async-context-manager,
                          not-context-manager,
                          attribute-defined-outside-init

# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes

# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1

# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1

# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin

# List of decorators that change the signature of a decorated function.
signature-mutators=


[VARIABLES]

# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=

# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes

# List of names allowed to shadow builtins
allowed-redefined-builtins=

# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
          _cb

# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_

# Argument names that match this expression will be ignored.
ignored-argument-names=_.*|^ignored_|^unused_

# Tells whether we should check for unused import in __init__ files.
init-import=no

# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io


================================================
FILE: setup.cfg
================================================
[metadata]
name = edge-tts
version = attr: edge_tts.version.__version__
author = rany
author_email = ranygh@riseup.net
description = Microsoft Edge's TTS
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/rany2/edge-tts
project_urls =
    Bug Tracker=https://github.com/rany2/edge-tts/issues
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
    Operating System :: OS Independent

[options]
package_dir=
    = src
packages = find:
python_requires = >=3.7
# Dependencies are in setup.py for GitHub's dependency graph.

[options.packages.find]
where=src
   
[options.entry_points]
console_scripts =
    edge-tts = edge_tts.__main__:main
    edge-playback = edge_playback.__main__:_main

[options.extras_require]
dev =
    black
    isort
    mypy
    pylint
    types-tabulate


================================================
FILE: setup.py
================================================
from setuptools import setup

# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.
setup(
    name="edge-tts",
    install_requires=[
        "aiohttp>=3.8.0,<4.0.0",
        "certifi>=2023.11.17",
        "tabulate>=0.4.4,<1.0.0",
        "typing-extensions>=4.1.0,<5.0.0",
    ],
)


================================================
FILE: src/edge_playback/__init__.py
================================================
"""The edge_playback package wraps the functionality of mpv and edge-tts to generate
text-to-speech (TTS) using edge-tts and then plays back the generated audio using mpv.
"""

from .__main__ import _main

__all__ = ["_main"]


================================================
FILE: src/edge_playback/__main__.py
================================================
"""Main entrypoint for the edge-playback package."""

import argparse
import os
import subprocess
import sys
import tempfile
from shutil import which
from typing import List, Optional, Tuple

from .util import pr_err


def _parse_args() -> Tuple[bool, List[str]]:
    parser = argparse.ArgumentParser(
        prog="edge-playback",
        description="Speak text using Microsoft Edge's online text-to-speech API",
        epilog="See `edge-tts` for additional arguments",
    )
    parser.add_argument(
        "--mpv",
        action="store_true",
        help="Use mpv to play audio. By default, false on Windows and true on all other platforms",
    )
    args, tts_args = parser.parse_known_args()
    use_mpv = sys.platform != "win32" or args.mpv
    return use_mpv, tts_args


def _check_deps(use_mpv: bool) -> None:
    depcheck_failed = False
    deps = ["edge-tts"]
    if use_mpv:
        deps.append("mpv")

    for dep in deps:
        if not which(dep):
            pr_err(f"{dep} is not installed.")
            depcheck_failed = True

    if depcheck_failed:
        pr_err("Please install the missing dependencies.")
        sys.exit(1)


def _create_temp_files(
    use_mpv: bool, mp3_fname: Optional[str], srt_fname: Optional[str], debug: bool
) -> Tuple[str, Optional[str]]:
    media = subtitle = None
    if not mp3_fname:
        media = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
        media.close()
        mp3_fname = media.name
        if debug:
            print(f"Media file: {mp3_fname}")

    if not srt_fname and use_mpv:
        subtitle = tempfile.NamedTemporaryFile(suffix=".srt", delete=False)
        subtitle.close()
        srt_fname = subtitle.name

    if debug and srt_fname:
        print(f"Subtitle file: {srt_fname}\n")

    return mp3_fname, srt_fname


def _run_edge_tts(
    mp3_fname: str, srt_fname: Optional[str], tts_args: List[str]
) -> None:
    edge_tts_cmd = ["edge-tts", f"--write-media={mp3_fname}"]
    if srt_fname:
        edge_tts_cmd.append(f"--write-subtitles={srt_fname}")
    edge_tts_cmd = edge_tts_cmd + tts_args
    with subprocess.Popen(edge_tts_cmd) as process:
        process.communicate()


def _play_media(use_mpv: bool, mp3_fname: str, srt_fname: Optional[str]) -> None:
    if sys.platform == "win32" and not use_mpv:
        # pylint: disable-next=import-outside-toplevel
        from .win32_playback import play_mp3_win32

        play_mp3_win32(mp3_fname)
        return

    mpv_cmd = [
        "mpv",
        "--msg-level=all=error,statusline=status",
    ]
    if srt_fname:
        mpv_cmd.append(f"--sub-file={srt_fname}")
    mpv_cmd.append(mp3_fname)
    with subprocess.Popen(mpv_cmd) as process:
        process.communicate()


def _cleanup(mp3_fname: Optional[str], srt_fname: Optional[str], keep: bool) -> None:
    if keep and mp3_fname is not None:
        print(f"\nKeeping temporary files: {mp3_fname}", end="")
        if srt_fname:
            print(f" and {srt_fname}", end="")
        print()
        return

    if mp3_fname is not None and os.path.exists(mp3_fname):
        os.unlink(mp3_fname)
    if srt_fname is not None and os.path.exists(srt_fname):
        os.unlink(srt_fname)


def _main() -> None:
    use_mpv, tts_args = _parse_args()
    _check_deps(use_mpv)

    debug = os.environ.get("EDGE_PLAYBACK_DEBUG") is not None
    keep = os.environ.get("EDGE_PLAYBACK_KEEP_TEMP") is not None
    mp3_fname = os.environ.get("EDGE_PLAYBACK_MP3_FILE")
    srt_fname = os.environ.get("EDGE_PLAYBACK_SRT_FILE")

    try:
        mp3_fname, srt_fname = _create_temp_files(use_mpv, mp3_fname, srt_fname, debug)
        _run_edge_tts(mp3_fname, srt_fname, tts_args)
        _play_media(use_mpv, mp3_fname, srt_fname)
    finally:
        _cleanup(mp3_fname, srt_fname, keep)


if __name__ == "__main__":
    _main()


================================================
FILE: src/edge_playback/py.typed
================================================


================================================
FILE: src/edge_playback/util.py
================================================
"""Utility functions for edge-playback"""

import sys


def pr_err(msg: str) -> None:
    """Print to stderr."""
    print(msg, file=sys.stderr)


================================================
FILE: src/edge_playback/win32_playback.py
================================================
"""Functions to play audio on Windows using native win32 APIs"""

import sys

from .util import pr_err


def play_mp3_win32(mp3_fname: str) -> None:
    """Play mp3 file with given path using win32 API"""

    if sys.platform != "win32":
        raise NotImplementedError("Function only available on Windows")

    # pylint: disable-next=import-outside-toplevel
    from ctypes import create_unicode_buffer, windll, wintypes  # type: ignore

    _get_short_path_name_w = windll.kernel32.GetShortPathNameW
    _get_short_path_name_w.argtypes = [
        wintypes.LPCWSTR,
        wintypes.LPWSTR,
        wintypes.DWORD,
    ]
    _get_short_path_name_w.restype = wintypes.DWORD

    def get_short_path_name(long_name: str) -> str:
        """
        Gets the DOS-safe short path name of a given long path.
        http://stackoverflow.com/a/23598461/200291
        """
        output_buf_size = 0
        while True:
            output_buf = create_unicode_buffer(output_buf_size)
            needed = _get_short_path_name_w(long_name, output_buf, output_buf_size)
            if output_buf_size >= needed:
                return output_buf.value
            output_buf_size = needed

    mci_send_string_w = windll.winmm.mciSendStringW

    def mci_send(msg: str) -> None:
        """Send MCI command string"""
        result = mci_send_string_w(msg, 0, 0, 0)
        if result != 0:
            pr_err(f"Error {result} in mciSendString {msg}. Exiting.")
            sys.exit(1)

    mp3_shortname = get_short_path_name(mp3_fname)

    mci_send("Close All")
    mci_send(f'Open "{mp3_shortname}" Type MPEGVideo Alias theMP3')
    mci_send("Play theMP3 Wait")
    mci_send("Close theMP3")


================================================
FILE: src/edge_tts/__init__.py
================================================
"""edge-tts allows you to use Microsoft Edge's online text-to-speech service without
needing Windows or the Edge browser."""

from . import exceptions
from .communicate import Communicate
from .submaker import SubMaker
from .version import __version__, __version_info__
from .voices import VoicesManager, list_voices

__all__ = [
    "Communicate",
    "SubMaker",
    "exceptions",
    "__version__",
    "__version_info__",
    "VoicesManager",
    "list_voices",
]


================================================
FILE: src/edge_tts/__main__.py
================================================
"""Main entrypoint for the edge-tts package."""

from .util import main

if __name__ == "__main__":
    main()


================================================
FILE: src/edge_tts/communicate.py
================================================
"""Communicate with the service. Only the Communicate class should be used by
end-users. The other classes and functions are for internal use only."""

import asyncio
import concurrent.futures
import json
import ssl
import time
import uuid
from contextlib import nullcontext
from io import TextIOWrapper
from queue import Queue
from typing import (
    AsyncGenerator,
    ContextManager,
    Dict,
    Generator,
    List,
    Optional,
    Tuple,
    Union,
)
from xml.sax.saxutils import escape, unescape

import aiohttp
import certifi
from typing_extensions import Literal

from .constants import DEFAULT_VOICE, SEC_MS_GEC_VERSION, WSS_HEADERS, WSS_URL
from .data_classes import TTSConfig
from .drm import DRM
from .exceptions import (
    NoAudioReceived,
    UnexpectedResponse,
    UnknownResponse,
    WebSocketError,
)
from .typing import CommunicateState, TTSChunk


def get_headers_and_data(
    data: bytes, header_length: int
) -> Tuple[Dict[bytes, bytes], bytes]:
    """
    Returns the headers and data from the given data.

    Args:
        data (bytes): The data to be parsed.
        header_length (int): The length of the header.

    Returns:
        tuple: The headers and data to be used in the request.
    """
    if not isinstance(data, bytes):
        raise TypeError("data must be bytes")

    headers = {}
    for line in data[:header_length].split(b"\r\n"):
        key, value = line.split(b":", 1)
        headers[key] = value

    return headers, data[header_length + 2 :]


def remove_incompatible_characters(string: Union[str, bytes]) -> str:
    """
    The service does not support a couple character ranges.
    Most important being the vertical tab character which is
    commonly present in OCR-ed PDFs. Not doing this will
    result in an error from the service.

    Args:
        string (str or bytes): The string to be cleaned.

    Returns:
        str: The cleaned string.
    """
    if isinstance(string, bytes):
        string = string.decode("utf-8")
    if not isinstance(string, str):
        raise TypeError("string must be str or bytes")

    chars: List[str] = list(string)

    for idx, char in enumerate(chars):
        code: int = ord(char)
        if (0 <= code <= 8) or (11 <= code <= 12) or (14 <= code <= 31):
            chars[idx] = " "

    return "".join(chars)


def connect_id() -> str:
    """
    Returns a UUID without dashes.

    Returns:
        str: A UUID without dashes.
    """
    return uuid.uuid4().hex


def _find_last_newline_or_space_within_limit(text: bytes, limit: int) -> int:
    """
    Finds the index of the rightmost preferred split character (newline or space)
    within the initial `limit` bytes of the text.

    This helps find a natural word or sentence boundary for splitting, prioritizing
    newlines over spaces.

    Args:
        text (bytes): The byte string to search within.
        limit (int): The maximum index (exclusive) to search up to.

    Returns:
        int: The index of the last found newline or space within the limit,
             or -1 if neither is found in that range.
    """
    # Prioritize finding a newline character
    split_at = text.rfind(b"\n", 0, limit)
    # If no newline is found, search for a space
    if split_at < 0:
        split_at = text.rfind(b" ", 0, limit)
    return split_at


def _find_safe_utf8_split_point(text_segment: bytes) -> int:
    """
    Finds the rightmost possible byte index such that the
    segment `text_segment[:index]` is a valid UTF-8 sequence.

    This prevents splitting in the middle of a multi-byte UTF-8 character.

    Args:
        text_segment (bytes): The byte segment being considered for splitting.

    Returns:
        int: The index of the safe split point. Returns 0 if no valid split
             point is found (e.g., if the first byte is part of a multi-byte
             sequence longer than the limit allows).
    """
    split_at = len(text_segment)
    while split_at > 0:
        try:
            text_segment[:split_at].decode("utf-8")
            # Found the largest valid UTF-8 sequence
            return split_at
        except UnicodeDecodeError:
            # The byte at split_at-1 is part of an incomplete multi-byte char, try earlier
            split_at -= 1

    return split_at


def _adjust_split_point_for_xml_entity(text: bytes, split_at: int) -> int:
    """
    Adjusts a proposed split point backward to prevent splitting inside an XML entity.

    For example, if `text` is `b"this &amp; that"` and `split_at` falls between
    `&` and `;`, this function moves `split_at` to the index before `&`.

    Args:
        text (bytes): The text segment being considered.
        split_at (int): The proposed split point index, determined by whitespace
                        or UTF-8 safety.

    Returns:
        int: The adjusted split point index. It will be moved to the '&'
             if an unterminated entity is detected right before the original `split_at`.
             Otherwise, the original `split_at` is returned.
    """
    while split_at > 0 and b"&" in text[:split_at]:
        ampersand_index = text.rindex(b"&", 0, split_at)
        # Check if a semicolon exists between the ampersand and the split point
        if text.find(b";", ampersand_index, split_at) != -1:
            # Found a terminated entity (like &amp;), safe to break at original split_at
            break

        # Ampersand is not terminated before split_at, move split_at to it
        split_at = ampersand_index

    return split_at


def split_text_by_byte_length(
    text: Union[str, bytes], byte_length: int
) -> Generator[bytes, None, None]:
    """
    Splits text into chunks, each not exceeding a maximum byte length.

    This function prioritizes splitting at natural boundaries (newlines, spaces)
    while ensuring that:
    1. No chunk exceeds `byte_length` bytes.
    2. Chunks do not end with an incomplete UTF-8 multi-byte character.
    3. Chunks do not split XML entities (like `&amp;`) in the middle.

    Args:
        text (str or bytes): The input text. If str, it's encoded to UTF-8.
        byte_length (int): The maximum allowed byte length for any yielded chunk.
                           Must be positive.

    Yields:
        bytes: Text chunks (UTF-8 encoded, stripped of leading/trailing whitespace)
               that conform to the byte length and integrity constraints.

    Raises:
        TypeError: If `text` is not str or bytes.
        ValueError: If `byte_length` is not positive, or if a split point
                    cannot be determined (e.g., due to extremely small byte_length
                    relative to character/entity sizes).
    """
    if isinstance(text, str):
        text = text.encode("utf-8")
    if not isinstance(text, bytes):
        raise TypeError("text must be str or bytes")

    if byte_length <= 0:
        raise ValueError("byte_length must be greater than 0")

    while len(text) > byte_length:
        # Find the initial split point based on whitespace or UTF-8 boundary
        split_at = _find_last_newline_or_space_within_limit(text, byte_length)

        if split_at < 0:
            ## No newline or space found, so we need to find a safe UTF-8 split point
            split_at = _find_safe_utf8_split_point(text)

        # Adjust the split point to avoid cutting in the middle of an xml entity, such as '&amp;'
        split_at = _adjust_split_point_for_xml_entity(text, split_at)

        if split_at < 0:
            # This should not happen if byte_length is reasonable,
            # but guards against edge cases.
            raise ValueError(
                "Maximum byte length is too small or "
                "invalid text structure near '&' or invalid UTF-8"
            )

        # Yield the chunk
        chunk = text[:split_at].strip()
        if chunk:
            yield chunk

        # Prepare for the next iteration
        # If split_at became 0 after adjustment, advance by 1 to avoid infinite loop
        text = text[split_at if split_at > 0 else 1 :]

    # Yield the remaining part
    remaining_chunk = text.strip()
    if remaining_chunk:
        yield remaining_chunk


def mkssml(tc: TTSConfig, escaped_text: Union[str, bytes]) -> str:
    """
    Creates a SSML string from the given parameters.

    Args:
        tc (TTSConfig): The TTS configuration.
        escaped_text (str or bytes): The escaped text. If bytes, it must be UTF-8 encoded.

    Returns:
        str: The SSML string.
    """
    if isinstance(escaped_text, bytes):
        escaped_text = escaped_text.decode("utf-8")

    return (
        "<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'>"
        f"<voice name='{tc.voice}'>"
        f"<prosody pitch='{tc.pitch}' rate='{tc.rate}' volume='{tc.volume}'>"
        f"{escaped_text}"
        "</prosody>"
        "</voice>"
        "</speak>"
    )


def date_to_string() -> str:
    """
    Return Javascript-style date string.

    Returns:
        str: Javascript-style date string.
    """
    # %Z is not what we want, but it's the only way to get the timezone
    # without having to use a library. We'll just use UTC and hope for the best.
    # For example, right now %Z would return EEST when we need it to return
    # Eastern European Summer Time.
    return time.strftime(
        "%a %b %d %Y %H:%M:%S GMT+0000 (Coordinated Universal Time)", time.gmtime()
    )


def ssml_headers_plus_data(request_id: str, timestamp: str, ssml: str) -> str:
    """
    Returns the headers and data to be used in the request.

    Returns:
        str: The headers and data to be used in the request.
    """

    return (
        f"X-RequestId:{request_id}\r\n"
        "Content-Type:application/ssml+xml\r\n"
        f"X-Timestamp:{timestamp}Z\r\n"  # This is not a mistake, Microsoft Edge bug.
        "Path:ssml\r\n\r\n"
        f"{ssml}"
    )


class Communicate:
    """
    Communicate with the service.
    """

    # pylint: disable=too-many-arguments
    def __init__(
        self,
        text: str,
        voice: str = DEFAULT_VOICE,
        *,
        rate: str = "+0%",
        volume: str = "+0%",
        pitch: str = "+0Hz",
        boundary: Literal["WordBoundary", "SentenceBoundary"] = "SentenceBoundary",
        connector: Optional[aiohttp.BaseConnector] = None,
        proxy: Optional[str] = None,
        connect_timeout: Optional[int] = 10,
        receive_timeout: Optional[int] = 60,
    ):
        # Validate TTS settings and store the TTSConfig object.
        self.tts_config = TTSConfig(voice, rate, volume, pitch, boundary)

        # Validate the text parameter.
        if not isinstance(text, str):
            raise TypeError("text must be str")

        # Split the text into multiple strings and store them.
        self.texts = split_text_by_byte_length(
            escape(remove_incompatible_characters(text)),
            4096,
        )

        # Validate the proxy parameter.
        if proxy is not None and not isinstance(proxy, str):
            raise TypeError("proxy must be str")
        self.proxy: Optional[str] = proxy

        # Validate the timeout parameters.
        if not isinstance(connect_timeout, int):
            raise TypeError("connect_timeout must be int")
        if not isinstance(receive_timeout, int):
            raise TypeError("receive_timeout must be int")
        self.session_timeout = aiohttp.ClientTimeout(
            total=None,
            connect=None,
            sock_connect=connect_timeout,
            sock_read=receive_timeout,
        )

        # Validate the connector parameter.
        if connector is not None and not isinstance(connector, aiohttp.BaseConnector):
            raise TypeError("connector must be aiohttp.BaseConnector")
        self.connector: Optional[aiohttp.BaseConnector] = connector

        # Store current state of TTS.
        self.state: CommunicateState = {
            "partial_text": b"",
            "offset_compensation": 0,
            "last_duration_offset": 0,
            "stream_was_called": False,
        }

    def __parse_metadata(self, data: bytes) -> TTSChunk:
        for meta_obj in json.loads(data)["Metadata"]:
            meta_type = meta_obj["Type"]
            if meta_type in ("WordBoundary", "SentenceBoundary"):
                current_offset = (
                    meta_obj["Data"]["Offset"] + self.state["offset_compensation"]
                )
                current_duration = meta_obj["Data"]["Duration"]
                return {
                    "type": meta_type,
                    "offset": current_offset,
                    "duration": current_duration,
                    "text": unescape(meta_obj["Data"]["text"]["Text"]),
                }
            if meta_type in ("SessionEnd",):
                continue
            raise UnknownResponse(f"Unknown metadata type: {meta_type}")
        raise UnexpectedResponse("No WordBoundary metadata found")

    async def __stream(self) -> AsyncGenerator[TTSChunk, None]:
        async def send_command_request() -> None:
            """Sends the command request to the service."""
            word_boundary = self.tts_config.boundary == "WordBoundary"
            wd = "true" if word_boundary else "false"
            sq = "true" if not word_boundary else "false"
            await websocket.send_str(
                f"X-Timestamp:{date_to_string()}\r\n"
                "Content-Type:application/json; charset=utf-8\r\n"
                "Path:speech.config\r\n\r\n"
                '{"context":{"synthesis":{"audio":{"metadataoptions":{'
                f'"sentenceBoundaryEnabled":"{sq}","wordBoundaryEnabled":"{wd}"'
                "},"
                '"outputFormat":"audio-24khz-48kbitrate-mono-mp3"'
                "}}}}\r\n"
            )

        async def send_ssml_request() -> None:
            """Sends the SSML request to the service."""
            await websocket.send_str(
                ssml_headers_plus_data(
                    connect_id(),
                    date_to_string(),
                    mkssml(
                        self.tts_config,
                        self.state["partial_text"],
                    ),
                )
            )

        # audio_was_received indicates whether we have received audio data
        # from the websocket. This is so we can raise an exception if we
        # don't receive any audio data.
        audio_was_received = False

        # Create a new connection to the service.
        ssl_ctx = ssl.create_default_context(cafile=certifi.where())
        async with aiohttp.ClientSession(
            connector=self.connector,
            trust_env=True,
            timeout=self.session_timeout,
        ) as session, session.ws_connect(
            f"{WSS_URL}&ConnectionId={connect_id()}"
            f"&Sec-MS-GEC={DRM.generate_sec_ms_gec()}"
            f"&Sec-MS-GEC-Version={SEC_MS_GEC_VERSION}",
            compress=15,
            proxy=self.proxy,
            headers=DRM.headers_with_muid(WSS_HEADERS),
            ssl=ssl_ctx,
        ) as websocket:
            await send_command_request()

            await send_ssml_request()

            async for received in websocket:
                if received.type == aiohttp.WSMsgType.TEXT:
                    encoded_data: bytes = received.data.encode("utf-8")
                    parameters, data = get_headers_and_data(
                        encoded_data, encoded_data.find(b"\r\n\r\n")
                    )

                    path = parameters.get(b"Path", None)
                    if path == b"audio.metadata":
                        # Parse the metadata and yield it.
                        parsed_metadata = self.__parse_metadata(data)
                        yield parsed_metadata

                        # Update the last duration offset for use by the next SSML request.
                        self.state["last_duration_offset"] = (
                            parsed_metadata["offset"] + parsed_metadata["duration"]
                        )
                    elif path == b"turn.end":
                        # Update the offset compensation for the next SSML request.
                        self.state["offset_compensation"] = self.state[
                            "last_duration_offset"
                        ]

                        # Use average padding typically added by the service
                        # to the end of the audio data. This seems to work pretty
                        # well for now, but we might ultimately need to use a
                        # more sophisticated method like using ffmpeg to get
                        # the actual duration of the audio data.
                        self.state["offset_compensation"] += 8_750_000

                        # Exit the loop so we can send the next SSML request.
                        break
                    elif path not in (b"response", b"turn.start"):
                        raise UnknownResponse("Unknown path received")
                elif received.type == aiohttp.WSMsgType.BINARY:
                    # Message is too short to contain header length.
                    if len(received.data) < 2:
                        raise UnexpectedResponse(
                            "We received a binary message, but it is missing the header length."
                        )

                    # The first two bytes of the binary message contain the header length.
                    header_length = int.from_bytes(received.data[:2], "big")
                    if header_length > len(received.data):
                        raise UnexpectedResponse(
                            "The header length is greater than the length of the data."
                        )

                    # Parse the headers and data from the binary message.
                    parameters, data = get_headers_and_data(
                        received.data, header_length
                    )

                    # Check if the path is audio.
                    if parameters.get(b"Path") != b"audio":
                        raise UnexpectedResponse(
                            "Received binary message, but the path is not audio."
                        )

                    # At termination of the stream, the service sends a binary message
                    # with no Content-Type; this is expected. What is not expected is for
                    # an audio stream to be sent with no data.
                    content_type = parameters.get(b"Content-Type", None)
                    if content_type not in (b"audio/mpeg", None):
                        raise UnexpectedResponse(
                            "Received binary message, but with an unexpected Content-Type."
                        )

                    # We only allow no Content-Type if there is no data.
                    if content_type is None:
                        if len(data) == 0:
                            continue

                        # If the data is not empty, then we need to raise an exception.
                        raise UnexpectedResponse(
                            "Received binary message with no Content-Type, but with data."
                        )

                    # If the data is empty now, then we need to raise an exception.
                    if len(data) == 0:
                        raise UnexpectedResponse(
                            "Received binary message, but it is missing the audio data."
                        )

                    # Yield the audio data.
                    audio_was_received = True
                    yield {"type": "audio", "data": data}
                elif received.type == aiohttp.WSMsgType.ERROR:
                    raise WebSocketError(
                        received.data if received.data else "Unknown error"
                    )

            if not audio_was_received:
                raise NoAudioReceived(
                    "No audio was received. Please verify that your parameters are correct."
                )

    async def stream(
        self,
    ) -> AsyncGenerator[TTSChunk, None]:
        """
        Streams audio and metadata from the service.

        Raises:
            NoAudioReceived: If no audio is received from the service.
            UnexpectedResponse: If the response from the service is unexpected.
            UnknownResponse: If the response from the service is unknown.
            WebSocketError: If there is an error with the websocket.
        """

        # Check if stream was called before.
        if self.state["stream_was_called"]:
            raise RuntimeError("stream can only be called once.")
        self.state["stream_was_called"] = True

        # Stream the audio and metadata from the service.
        for self.state["partial_text"] in self.texts:
            try:
                async for message in self.__stream():
                    yield message
            except aiohttp.ClientResponseError as e:
                if e.status != 403:
                    raise

                DRM.handle_client_response_error(e)
                async for message in self.__stream():
                    yield message

    async def save(
        self,
        audio_fname: Union[str, bytes],
        metadata_fname: Optional[Union[str, bytes]] = None,
    ) -> None:
        """
        Save the audio and metadata to the specified files.
        """
        metadata: Union[TextIOWrapper, ContextManager[None]] = (
            open(metadata_fname, "w", encoding="utf-8")
            if metadata_fname is not None
            else nullcontext()
        )
        with metadata, open(audio_fname, "wb") as audio:
            async for message in self.stream():
                if message["type"] == "audio":
                    audio.write(message["data"])
                elif isinstance(metadata, TextIOWrapper) and message["type"] in (
                    "WordBoundary",
                    "SentenceBoundary",
                ):
                    json.dump(message, metadata)
                    metadata.write("\n")

    def stream_sync(self) -> Generator[TTSChunk, None, None]:
        """Synchronous interface for async stream method"""

        def fetch_async_items(queue: Queue) -> None:  # type: ignore
            async def get_items() -> None:
                async for item in self.stream():
                    queue.put(item)
                queue.put(None)

            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.run_until_complete(get_items())
            loop.close()

        queue: Queue = Queue()  # type: ignore

        with concurrent.futures.ThreadPoolExecutor() as executor:
            executor.submit(fetch_async_items, queue)

            while True:
                item = queue.get()
                if item is None:
                    break
                yield item

    def save_sync(
        self,
        audio_fname: Union[str, bytes],
        metadata_fname: Optional[Union[str, bytes]] = None,
    ) -> None:
        """Synchronous interface for async save method."""
        with concurrent.futures.ThreadPoolExecutor() as executor:
            future = executor.submit(
                asyncio.run, self.save(audio_fname, metadata_fname)
            )
            future.result()


================================================
FILE: src/edge_tts/constants.py
================================================
"""Constants for the edge_tts package."""

BASE_URL = "speech.platform.bing.com/consumer/speech/synthesize/readaloud"
TRUSTED_CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4"

WSS_URL = f"wss://{BASE_URL}/edge/v1?TrustedClientToken={TRUSTED_CLIENT_TOKEN}"
VOICE_LIST = f"https://{BASE_URL}/voices/list?trustedclienttoken={TRUSTED_CLIENT_TOKEN}"

DEFAULT_VOICE = "en-US-EmmaMultilingualNeural"

CHROMIUM_FULL_VERSION = "143.0.3650.75"
CHROMIUM_MAJOR_VERSION = CHROMIUM_FULL_VERSION.split(".", maxsplit=1)[0]
SEC_MS_GEC_VERSION = f"1-{CHROMIUM_FULL_VERSION}"
BASE_HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    f" (KHTML, like Gecko) Chrome/{CHROMIUM_MAJOR_VERSION}.0.0.0 Safari/537.36"
    f" Edg/{CHROMIUM_MAJOR_VERSION}.0.0.0",
    "Accept-Encoding": "gzip, deflate, br, zstd",
    "Accept-Language": "en-US,en;q=0.9",
}
WSS_HEADERS = {
    "Pragma": "no-cache",
    "Cache-Control": "no-cache",
    "Origin": "chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold",
    "Sec-WebSocket-Version": "13",
}
WSS_HEADERS.update(BASE_HEADERS)
VOICE_HEADERS = {
    "Authority": "speech.platform.bing.com",
    "Sec-CH-UA": f'" Not;A Brand";v="99", "Microsoft Edge";v="{CHROMIUM_MAJOR_VERSION}",'
    f' "Chromium";v="{CHROMIUM_MAJOR_VERSION}"',
    "Sec-CH-UA-Mobile": "?0",
    "Accept": "*/*",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Dest": "empty",
}
VOICE_HEADERS.update(BASE_HEADERS)


================================================
FILE: src/edge_tts/data_classes.py
================================================
"""Data models for edge-tts."""

# pylint: disable=too-few-public-methods

import argparse
import re
from dataclasses import dataclass

from typing_extensions import Literal


@dataclass
class TTSConfig:
    """
    Represents the internal TTS configuration for edge-tts's Communicate class.
    """

    voice: str
    rate: str
    volume: str
    pitch: str
    boundary: Literal["WordBoundary", "SentenceBoundary"]

    @staticmethod
    def validate_string_param(param_name: str, param_value: str, pattern: str) -> str:
        """
        Validates the given string parameter based on type and pattern.

        Args:
            param_name (str): The name of the parameter.
            param_value (str): The value of the parameter.
            pattern (str): The pattern to validate the parameter against.

        Returns:
            str: The validated parameter.
        """
        if not isinstance(param_value, str):
            raise TypeError(f"{param_name} must be str")
        if re.match(pattern, param_value) is None:
            raise ValueError(f"Invalid {param_name} '{param_value}'.")
        return param_value

    def __post_init__(self) -> None:
        """
        Validates the TTSConfig object after initialization.
        """

        # Possible values for voice are:
        # - Microsoft Server Speech Text to Speech Voice (cy-GB, NiaNeural)
        # - cy-GB-NiaNeural
        # - fil-PH-AngeloNeural
        # Always send the first variant as that is what Microsoft Edge does.
        if not isinstance(self.voice, str):
            raise TypeError("voice must be str")
        match = re.match(r"^([a-z]{2,})-([A-Z]{2,})-(.+Neural)$", self.voice)
        if match is not None:
            lang = match.group(1)
            region = match.group(2)
            name = match.group(3)
            if name.find("-") != -1:
                region = f"{region}-{name[:name.find('-')]}"
                name = name[name.find("-") + 1 :]
            self.voice = (
                "Microsoft Server Speech Text to Speech Voice"
                + f" ({lang}-{region}, {name})"
            )

        # Validate the rate, volume, and pitch parameters.
        self.validate_string_param(
            "voice",
            self.voice,
            r"^Microsoft Server Speech Text to Speech Voice \(.+,.+\)$",
        )
        self.validate_string_param("rate", self.rate, r"^[+-]\d+%$")
        self.validate_string_param("volume", self.volume, r"^[+-]\d+%$")
        self.validate_string_param("pitch", self.pitch, r"^[+-]\d+Hz$")


class UtilArgs(argparse.Namespace):
    """CLI arguments."""

    text: str
    file: str
    voice: str
    list_voices: bool
    rate: str
    volume: str
    pitch: str
    write_media: str
    write_subtitles: str
    proxy: str


================================================
FILE: src/edge_tts/drm.py
================================================
"""DRM module is used to handle DRM operations with clock skew correction.
Currently the only DRM operation is generating the Sec-MS-GEC token value
used in all API requests to Microsoft Edge's online text-to-speech service."""

import hashlib
import secrets
from datetime import datetime as dt
from datetime import timezone as tz
from typing import Dict, Optional

import aiohttp

from .constants import TRUSTED_CLIENT_TOKEN
from .exceptions import SkewAdjustmentError

WIN_EPOCH = 11644473600
S_TO_NS = 1e9


class DRM:
    """
    Class to handle DRM operations with clock skew correction.
    """

    clock_skew_seconds: float = 0.0

    @staticmethod
    def adj_clock_skew_seconds(skew_seconds: float) -> None:
        """
        Adjust the clock skew in seconds in case the system clock is off.

        This method updates the `clock_skew_seconds` attribute of the DRM class
        to the specified number of seconds.

        Args:
            skew_seconds (float): The number of seconds to adjust the clock skew to.

        Returns:
            None
        """
        DRM.clock_skew_seconds += skew_seconds

    @staticmethod
    def get_unix_timestamp() -> float:
        """
        Gets the current timestamp in Unix format with clock skew correction.

        Returns:
            float: The current timestamp in Unix format with clock skew correction.
        """
        return dt.now(tz.utc).timestamp() + DRM.clock_skew_seconds

    @staticmethod
    def parse_rfc2616_date(date: str) -> Optional[float]:
        """
        Parses an RFC 2616 date string into a Unix timestamp.

        This function parses an RFC 2616 date string into a Unix timestamp.

        Args:
            date (str): RFC 2616 date string to parse.

        Returns:
            Optional[float]: Unix timestamp of the parsed date string, or None if parsing failed.
        """
        try:
            return (
                dt.strptime(date, "%a, %d %b %Y %H:%M:%S %Z")
                .replace(tzinfo=tz.utc)
                .timestamp()
            )
        except ValueError:
            return None

    @staticmethod
    def handle_client_response_error(e: aiohttp.ClientResponseError) -> None:
        """
        Handle a client response error.

        This method adjusts the clock skew based on the server date in the response headers
        and raises a SkewAdjustmentError if the server date is missing or invalid.

        Args:
            e (Exception): The client response error to handle.

        Returns:
            None
        """
        if e.headers is None:
            raise SkewAdjustmentError("No server date in headers.") from e
        server_date: Optional[str] = e.headers.get("Date", None)
        if server_date is None or not isinstance(server_date, str):
            raise SkewAdjustmentError("No server date in headers.") from e
        server_date_parsed: Optional[float] = DRM.parse_rfc2616_date(server_date)
        if server_date_parsed is None or not isinstance(server_date_parsed, float):
            raise SkewAdjustmentError(
                f"Failed to parse server date: {server_date}"
            ) from e
        client_date = DRM.get_unix_timestamp()
        DRM.adj_clock_skew_seconds(server_date_parsed - client_date)

    @staticmethod
    def generate_sec_ms_gec() -> str:
        """
        Generates the Sec-MS-GEC token value.

        This function generates a token value based on the current time in Windows file time format
        adjusted for clock skew, and rounded down to the nearest 5 minutes. The token is then hashed
        using SHA256 and returned as an uppercased hex digest.

        Returns:
            str: The generated Sec-MS-GEC token value.

        See Also:
            https://github.com/rany2/edge-tts/issues/290#issuecomment-2464956570
        """

        # Get the current timestamp in Unix format with clock skew correction
        ticks = DRM.get_unix_timestamp()

        # Switch to Windows file time epoch (1601-01-01 00:00:00 UTC)
        ticks += WIN_EPOCH

        # Round down to the nearest 5 minutes (300 seconds)
        ticks -= ticks % 300

        # Convert the ticks to 100-nanosecond intervals (Windows file time format)
        ticks *= S_TO_NS / 100

        # Create the string to hash by concatenating the ticks and the trusted client token
        str_to_hash = f"{ticks:.0f}{TRUSTED_CLIENT_TOKEN}"

        # Compute the SHA256 hash and return the uppercased hex digest
        return hashlib.sha256(str_to_hash.encode("ascii")).hexdigest().upper()

    @staticmethod
    def generate_muid() -> str:
        """
        Generates a random MUID.

        Returns:
            str: The generated MUID.
        """
        return secrets.token_hex(16).upper()

    @staticmethod
    def headers_with_muid(headers: Dict[str, str]) -> Dict[str, str]:
        """
        Returns a copy of the given headers with the MUID header added.

        Args:
            headers (dict): The original headers.

        Returns:
            dict: The headers with the MUID header added.
        """
        combined_headers = headers.copy()
        assert "Cookie" not in combined_headers
        combined_headers["Cookie"] = f"muid={DRM.generate_muid()};"
        return combined_headers


================================================
FILE: src/edge_tts/exceptions.py
================================================
"""Custom exceptions for the edge-tts package."""


class EdgeTTSException(Exception):
    """Base exception for the edge-tts package."""


class UnknownResponse(EdgeTTSException):
    """Raised when an unknown response is received from the server."""


class UnexpectedResponse(EdgeTTSException):
    """Raised when an unexpected response is received from the server.

    This hasn't happened yet, but it's possible that the server will
    change its response format in the future."""


class NoAudioReceived(EdgeTTSException):
    """Raised when no audio is received from the server."""


class WebSocketError(EdgeTTSException):
    """Raised when a WebSocket error occurs."""


class SkewAdjustmentError(EdgeTTSException):
    """Raised when an error occurs while adjusting the clock skew."""


================================================
FILE: src/edge_tts/py.typed
================================================


================================================
FILE: src/edge_tts/srt_composer.py
================================================
"""A tiny library for composing SRT files.

Based on https://github.com/cdown/srt with parsing, subtitle modifying
functionality and Python 2 support removed. This is because of
https://github.com/rany2/edge-tts/issues/383.

Typing support was added, and more Python 3 features were used.

Copyright (c) 2014-2023 Christopher Down
Copyright (c) 2025- rany <rany@riseup.net>

This file is licensed under the MIT License (MIT).
See the LICENSE-MIT file for details.
"""

import functools
import logging
import re
from datetime import timedelta
from typing import Generator, List, Union

LOG = logging.getLogger(__name__)

MULTI_WS_REGEX = re.compile(r"\n\n+")

ZERO_TIMEDELTA = timedelta(0)

# Info message if truthy return -> Function taking a Subtitle, skip if True
SUBTITLE_SKIP_CONDITIONS = (
    ("No content", lambda sub: not sub.content.strip()),
    ("Start time < 0 seconds", lambda sub: sub.start < ZERO_TIMEDELTA),
    ("Subtitle start time >= end time", lambda sub: sub.start >= sub.end),
)

SECONDS_IN_HOUR = 3600
SECONDS_IN_MINUTE = 60
HOURS_IN_DAY = 24
MICROSECONDS_IN_MILLISECOND = 1000


@functools.total_ordering
class Subtitle:
    r"""
    The metadata relating to a single subtitle. Subtitles are sorted by start
    time by default. If no index was provided, index 0 will be used on writing
    an SRT block.

    :param index: The SRT index for this subtitle
    :type index: int or None
    :param start: The time that the subtitle should start being shown
    :type start: :py:class:`datetime.timedelta`
    :param end: The time that the subtitle should stop being shown
    :type end: :py:class:`datetime.timedelta`
    :param str content: The subtitle content. Should not contain OS-specific
                        line separators, only \\n. This is taken care of
                        already if you use :py:func:`srt.parse` to generate
                        Subtitle objects.
    """

    # pylint: disable=R0913
    def __init__(
        self, index: Union[int, None], start: timedelta, end: timedelta, content: str
    ) -> None:
        self.index = index
        self.start = start
        self.end = end
        self.content = content

    def __hash__(self) -> int:
        return hash(frozenset(vars(self).items()))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Subtitle):
            return NotImplemented

        return vars(self) == vars(other)

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, Subtitle):
            return NotImplemented

        return (self.start, self.end, self.index) < (
            other.start,
            other.end,
            other.index,
        )

    def __repr__(self) -> str:
        # Python 2/3 cross compatibility
        var_items = getattr(vars(self), "iteritems", getattr(vars(self), "items"))
        item_list = ", ".join(f"{k}={v!r}" for k, v in var_items())
        return f"{type(self).__name__}({item_list})"

    def to_srt(self, eol: Union[str, None] = None) -> str:
        r"""
        Convert the current :py:class:`Subtitle` to an SRT block.

        :param str eol: The end of line string to use (default "\\n")
        :returns: The metadata of the current :py:class:`Subtitle` object as an
                  SRT formatted subtitle block
        :rtype: str
        """
        output_content = make_legal_content(self.content)

        if eol is None:
            eol = "\n"
        elif eol != "\n":
            output_content = output_content.replace("\n", eol)

        template = "{idx}{eol}{start} --> {end}{eol}{content}{eol}{eol}"
        return template.format(
            idx=self.index or 0,
            start=timedelta_to_srt_timestamp(self.start),
            end=timedelta_to_srt_timestamp(self.end),
            content=output_content,
            eol=eol,
        )


def make_legal_content(content: str) -> str:
    r"""
    Remove illegal content from a content block. Illegal content includes:

    * Blank lines
    * Starting or ending with a blank line

    .. doctest::

        >>> make_legal_content('\nfoo\n\nbar\n')
        'foo\nbar'

    :param str content: The content to make legal
    :returns: The legalised content
    :rtype: srt
    """
    # Optimisation: Usually the content we get is legally valid. Do a quick
    # check to see if we really need to do anything here. This saves time from
    # generating legal_content by about 50%.
    if content and content[0] != "\n" and "\n\n" not in content:
        return content

    legal_content = MULTI_WS_REGEX.sub("\n", content.strip("\n"))
    LOG.info("Legalised content %r to %r", content, legal_content)
    return legal_content


def timedelta_to_srt_timestamp(timedelta_timestamp: timedelta) -> str:
    r"""
    Convert a :py:class:`~datetime.timedelta` to an SRT timestamp.

    .. doctest::

        >>> import datetime
        >>> delta = datetime.timedelta(hours=1, minutes=23, seconds=4)
        >>> timedelta_to_srt_timestamp(delta)
        '01:23:04,000'

    :param datetime.timedelta timedelta_timestamp: A datetime to convert to an
                                                   SRT timestamp
    :returns: The timestamp in SRT format
    :rtype: str
    """

    hrs, secs_remainder = divmod(timedelta_timestamp.seconds, SECONDS_IN_HOUR)
    hrs += timedelta_timestamp.days * HOURS_IN_DAY
    mins, secs = divmod(secs_remainder, SECONDS_IN_MINUTE)
    msecs = timedelta_timestamp.microseconds // MICROSECONDS_IN_MILLISECOND
    return f"{int(hrs):02}:{int(mins):02}:{int(secs):02},{int(msecs):03}"


def sort_and_reindex(
    subtitles: Union[Generator[Subtitle, None, None], List[Subtitle]],
    start_index: int = 1,
    in_place: bool = False,
    skip: bool = True,
) -> Generator[Subtitle, None, None]:
    """
    Reorder subtitles to be sorted by start time order, and rewrite the indexes
    to be in that same order. This ensures that the SRT file will play in an
    expected fashion after, for example, times were changed in some subtitles
    and they may need to be resorted.

    If skip=True, subtitles will also be skipped if they are considered not to
    be useful. Currently, the conditions to be considered "not useful" are as
    follows:

    - Content is empty, or only whitespace
    - The start time is negative
    - The start time is equal to or later than the end time

    .. doctest::

        >>> from datetime import timedelta
        >>> one = timedelta(seconds=1)
        >>> two = timedelta(seconds=2)
        >>> three = timedelta(seconds=3)
        >>> subs = [
        ...     Subtitle(index=999, start=one, end=two, content='1'),
        ...     Subtitle(index=0, start=two, end=three, content='2'),
        ... ]
        >>> list(sort_and_reindex(subs))  # doctest: +ELLIPSIS
        [Subtitle(...index=1...), Subtitle(...index=2...)]

    :param subtitles: :py:class:`Subtitle` objects in any order
    :param int start_index: The index to start from
    :param bool in_place: Whether to modify subs in-place for performance
                          (version <=1.0.0 behaviour)
    :param bool skip: Whether to skip subtitles considered not useful (see
                      above for rules)
    :returns: The sorted subtitles
    :rtype: :term:`generator` of :py:class:`Subtitle` objects
    """
    skipped_subs = 0
    for sub_num, subtitle in enumerate(sorted(subtitles), start=start_index):
        if not in_place:
            subtitle = Subtitle(**vars(subtitle))

        if skip:
            try:
                _should_skip_sub(subtitle)
            except _ShouldSkipException as thrown_exc:
                if subtitle.index is None:
                    LOG.info("Skipped subtitle with no index: %s", thrown_exc)
                else:
                    LOG.info(
                        "Skipped subtitle at index %d: %s", subtitle.index, thrown_exc
                    )
                skipped_subs += 1
                continue

        subtitle.index = sub_num - skipped_subs

        yield subtitle


def _should_skip_sub(subtitle: Subtitle) -> None:
    """
    Check if a subtitle should be skipped based on the rules in
    SUBTITLE_SKIP_CONDITIONS.

    :param subtitle: A :py:class:`Subtitle` to check whether to skip
    :raises _ShouldSkipException: If the subtitle should be skipped
    """
    for info_msg, sub_skipper in SUBTITLE_SKIP_CONDITIONS:
        if sub_skipper(subtitle):
            raise _ShouldSkipException(info_msg)


def compose(
    subtitles: Union[Generator[Subtitle, None, None], List[Subtitle]],
    reindex: bool = True,
    start_index: int = 1,
    eol: Union[str, None] = None,
    in_place: bool = False,
) -> str:
    r"""
    Convert an iterator of :py:class:`Subtitle` objects to a string of joined
    SRT blocks.

    .. doctest::

        >>> from datetime import timedelta
        >>> start = timedelta(seconds=1)
        >>> end = timedelta(seconds=2)
        >>> subs = [
        ...     Subtitle(index=1, start=start, end=end, content='x'),
        ...     Subtitle(index=2, start=start, end=end, content='y'),
        ... ]
        >>> compose(subs)  # doctest: +ELLIPSIS
        '1\n00:00:01,000 --> 00:00:02,000\nx\n\n2\n00:00:01,000 --> ...'

    :param subtitles: The subtitles to convert to SRT blocks
    :type subtitles: :term:`iterator` of :py:class:`Subtitle` objects
    :param bool reindex: Whether to reindex subtitles based on start time
    :param int start_index: If reindexing, the index to start reindexing from
    :param str eol: The end of line string to use (default "\\n")
    :returns: A single SRT formatted string, with each input
              :py:class:`Subtitle` represented as an SRT block
    :param bool in_place: Whether to reindex subs in-place for performance
                          (version <=1.0.0 behaviour)
    :rtype: str
    """
    if reindex:
        subtitles = sort_and_reindex(
            subtitles, start_index=start_index, in_place=in_place
        )

    return "".join(subtitle.to_srt(eol=eol) for subtitle in subtitles)


class _ShouldSkipException(Exception):
    """
    Raised when a subtitle should be skipped.
    """


================================================
FILE: src/edge_tts/submaker.py
================================================
"""SubMaker module is used to generate subtitles from WordBoundary and SentenceBoundary events."""

from datetime import timedelta
from typing import List, Optional

from .srt_composer import Subtitle, compose
from .typing import TTSChunk


class SubMaker:
    """
    SubMaker is used to generate subtitles from WordBoundary and SentenceBoundary messages.
    """

    def __init__(self) -> None:
        self.cues: List[Subtitle] = []
        self.type: Optional[str] = None

    def feed(self, msg: TTSChunk) -> None:
        """
        Feed a WordBoundary or SentenceBoundary message to the SubMaker object.

        Args:
            msg (dict): The WordBoundary or SentenceBoundary message.

        Returns:
            None
        """
        if msg["type"] not in ("WordBoundary", "SentenceBoundary"):
            raise ValueError(
                "Invalid message type, expected 'WordBoundary' or 'SentenceBoundary'."
            )

        if self.type is None:
            self.type = msg["type"]
        elif self.type != msg["type"]:
            raise ValueError(
                f"Expected message type '{self.type}', but got '{msg['type']}'."
            )

        self.cues.append(
            Subtitle(
                index=len(self.cues) + 1,
                start=timedelta(microseconds=msg["offset"] / 10),
                end=timedelta(microseconds=(msg["offset"] + msg["duration"]) / 10),
                content=msg["text"],
            )
        )

    def get_srt(self) -> str:
        """
        Get the SRT formatted subtitles from the SubMaker object.

        Returns:
            str: The SRT formatted subtitles.
        """
        return compose(self.cues)

    def __str__(self) -> str:
        return self.get_srt()


================================================
FILE: src/edge_tts/typing.py
================================================
"""Custom types for edge-tts."""

# pylint: disable=too-few-public-methods

from typing import List

from typing_extensions import Literal, NotRequired, TypedDict


class TTSChunk(TypedDict):
    """TTS chunk data."""

    type: Literal["audio", "WordBoundary", "SentenceBoundary"]
    data: NotRequired[bytes]  # only for audio
    duration: NotRequired[float]  # only for WordBoundary and SentenceBoundary
    offset: NotRequired[float]  # only for WordBoundary and SentenceBoundary
    text: NotRequired[str]  # only for WordBoundary and SentenceBoundary


class VoiceTag(TypedDict):
    """VoiceTag data."""

    ContentCategories: List[str]
    VoicePersonalities: List[str]


class Voice(TypedDict):
    """Voice data."""

    Name: str
    ShortName: str
    Gender: Literal["Female", "Male"]
    Locale: str
    SuggestedCodec: str
    FriendlyName: str
    Status: Literal["Deprecated", "GA", "Preview"]
    VoiceTag: VoiceTag


class VoicesManagerVoice(Voice):
    """Voice data for VoicesManager."""

    Language: str


class VoicesManagerFind(TypedDict):
    """Voice data for VoicesManager.find()."""

    Gender: NotRequired[Literal["Female", "Male"]]
    Locale: NotRequired[str]
    Language: NotRequired[str]


class CommunicateState(TypedDict):
    """Communicate state data."""

    partial_text: bytes
    offset_compensation: float
    last_duration_offset: float
    stream_was_called: bool


================================================
FILE: src/edge_tts/util.py
================================================
"""Utility functions for the command line interface. Used by the main module."""

import argparse
import asyncio
import sys
from typing import Optional, TextIO

from tabulate import tabulate

from . import Communicate, SubMaker, list_voices
from .constants import DEFAULT_VOICE
from .data_classes import UtilArgs
from .version import __version__


async def _print_voices(*, proxy: Optional[str]) -> None:
    """Print all available voices."""
    voices = await list_voices(proxy=proxy)
    voices = sorted(voices, key=lambda voice: voice["ShortName"])
    headers = ["Name", "Gender", "ContentCategories", "VoicePersonalities"]
    table = [
        [
            voice["ShortName"],
            voice["Gender"],
            ", ".join(voice["VoiceTag"]["ContentCategories"]),
            ", ".join(voice["VoiceTag"]["VoicePersonalities"]),
        ]
        for voice in voices
    ]
    print(tabulate(table, headers))


async def _run_tts(args: UtilArgs) -> None:
    """Run TTS after parsing arguments from command line."""

    try:
        if sys.stdin.isatty() and sys.stdout.isatty() and not args.write_media:
            print(
                "Warning: TTS output will be written to the terminal. "
                "Use --write-media to write to a file.\n"
                "Press Ctrl+C to cancel the operation. "
                "Press Enter to continue.",
                file=sys.stderr,
            )
            input()
    except KeyboardInterrupt:
        print("\nOperation canceled.", file=sys.stderr)
        return

    communicate = Communicate(
        args.text,
        args.voice,
        rate=args.rate,
        volume=args.volume,
        pitch=args.pitch,
        proxy=args.proxy,
    )
    submaker = SubMaker()
    try:
        audio_file = (
            open(args.write_media, "wb")
            if args.write_media is not None and args.write_media != "-"
            else sys.stdout.buffer
        )
        sub_file: Optional[TextIO] = (
            open(args.write_subtitles, "w", encoding="utf-8")
            if args.write_subtitles is not None and args.write_subtitles != "-"
            else None
        )
        if sub_file is None and args.write_subtitles == "-":
            sub_file = sys.stderr

        async for chunk in communicate.stream():
            if chunk["type"] == "audio":
                audio_file.write(chunk["data"])
            elif chunk["type"] in ("WordBoundary", "SentenceBoundary"):
                submaker.feed(chunk)

        if sub_file is not None:
            sub_file.write(submaker.get_srt())
    finally:
        if audio_file is not sys.stdout.buffer:
            audio_file.close()
        if sub_file is not None and sub_file is not sys.stderr:
            sub_file.close()


async def amain() -> None:
    """Async main function"""
    parser = argparse.ArgumentParser(
        description="Text-to-speech using Microsoft Edge's online TTS service."
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("-t", "--text", help="what TTS will say")
    group.add_argument("-f", "--file", help="same as --text but read from file")
    parser.add_argument(
        "-v",
        "--voice",
        help=f"voice for TTS. Default: {DEFAULT_VOICE}",
        default=DEFAULT_VOICE,
    )
    group.add_argument(
        "-l",
        "--list-voices",
        help="lists available voices and exits",
        action="store_true",
    )
    parser.add_argument("--rate", help="set TTS rate. Default +0%%.", default="+0%")
    parser.add_argument("--volume", help="set TTS volume. Default +0%%.", default="+0%")
    parser.add_argument("--pitch", help="set TTS pitch. Default +0Hz.", default="+0Hz")
    parser.add_argument(
        "--write-media", help="send media output to file instead of stdout"
    )
    parser.add_argument(
        "--write-subtitles",
        help="send subtitle output to provided file instead of stderr",
    )
    parser.add_argument("--proxy", help="use a proxy for TTS and voice list.")
    parser.add_argument(
        "--version", action="version", version=f"edge-tts {__version__}"
    )
    args = parser.parse_args(namespace=UtilArgs())

    if args.list_voices:
        await _print_voices(proxy=args.proxy)
        sys.exit(0)

    if args.file is not None:
        if args.file in ("-", "/dev/stdin"):
            args.text = sys.stdin.read()
        else:
            with open(args.file, encoding="utf-8") as file:
                args.text = file.read()

    if args.text is not None:
        await _run_tts(args)


def main() -> None:
    """Run the main function using asyncio."""
    asyncio.run(amain())


if __name__ == "__main__":
    main()


================================================
FILE: src/edge_tts/version.py
================================================
"""Version information for the edge_tts package."""

__version__ = "7.2.7"
__version_info__ = tuple(int(num) for num in __version__.split("."))


================================================
FILE: src/edge_tts/voices.py
================================================
"""This module contains functions to list all available voices and a class to find the
correct voice based on their attributes."""

import json
import ssl
from typing import Any, List, Optional

import aiohttp
import certifi
from typing_extensions import Unpack

from .constants import SEC_MS_GEC_VERSION, VOICE_HEADERS, VOICE_LIST
from .drm import DRM
from .typing import Voice, VoicesManagerFind, VoicesManagerVoice


async def __list_voices(
    session: aiohttp.ClientSession, ssl_ctx: ssl.SSLContext, proxy: Optional[str]
) -> List[Voice]:
    """
    Private function that makes the request to the voice list URL and parses the
    JSON response. This function is used by list_voices() and makes it easier to
    handle client response errors related to clock skew.

    Args:
        session (aiohttp.ClientSession): The aiohttp session to use for the request.
        ssl_ctx (ssl.SSLContext): The SSL context to use for the request.
        proxy (Optional[str]): The proxy to use for the request.

    Returns:
        List[Voice]: A list of voices and their attributes.
    """
    async with session.get(
        f"{VOICE_LIST}&Sec-MS-GEC={DRM.generate_sec_ms_gec()}"
        f"&Sec-MS-GEC-Version={SEC_MS_GEC_VERSION}",
        headers=DRM.headers_with_muid(VOICE_HEADERS),
        proxy=proxy,
        ssl=ssl_ctx,
        raise_for_status=True,
    ) as url:
        data: List[Any] = json.loads(await url.text())

    for voice in data:
        if "VoiceTag" not in voice:
            voice["VoiceTag"] = {}

        if "ContentCategories" not in voice["VoiceTag"]:
            voice["VoiceTag"]["ContentCategories"] = []

        if "VoicePersonalities" not in voice["VoiceTag"]:
            voice["VoiceTag"]["VoicePersonalities"] = []

    return data


async def list_voices(
    *, connector: Optional[aiohttp.BaseConnector] = None, proxy: Optional[str] = None
) -> List[Voice]:
    """
    List all available voices and their attributes.

    This pulls data from the URL used by Microsoft Edge to return a list of
    all available voices.

    Args:
        connector (Optional[aiohttp.BaseConnector]): The connector to use for the request.
        proxy (Optional[str]): The proxy to use for the request.

    Returns:
        List[Voice]: A list of voices and their attributes.
    """
    ssl_ctx = ssl.create_default_context(cafile=certifi.where())
    async with aiohttp.ClientSession(connector=connector, trust_env=True) as session:
        try:
            data = await __list_voices(session, ssl_ctx, proxy)
        except aiohttp.ClientResponseError as e:
            if e.status != 403:
                raise

            DRM.handle_client_response_error(e)
            data = await __list_voices(session, ssl_ctx, proxy)
    return data


class VoicesManager:
    """
    A class to find the correct voice based on their attributes.
    """

    def __init__(self) -> None:
        self.voices: List[VoicesManagerVoice] = []
        self.called_create: bool = False

    @classmethod
    async def create(
        cls, custom_voices: Optional[List[Voice]] = None
    ) -> "VoicesManager":
        """
        Creates a VoicesManager object and populates it with all available voices.
        """
        self = VoicesManager()
        voices = await list_voices() if custom_voices is None else custom_voices
        self.voices = [
            {**voice, "Language": voice["Locale"].split("-")[0]} for voice in voices
        ]
        self.called_create = True
        return self

    def find(self, **kwargs: Unpack[VoicesManagerFind]) -> List[VoicesManagerVoice]:
        """
        Finds all matching voices based on the provided attributes.
        """
        if not self.called_create:
            raise RuntimeError(
                "VoicesManager.find() called before VoicesManager.create()"
            )

        matching_voices = [
            voice for voice in self.voices if kwargs.items() <= voice.items()
        ]
        return matching_voices


================================================
FILE: tests/001-long-text.sh
================================================
#!/usr/bin/env bash

# test if prompt file exists
if ! [[ -f "tests/001-long-text.txt" ]]
then
    echo "File not found!"
    exit 1
fi

# spawn
for i in {a..z}
do
    edge-tts -f tests/001-long-text.txt --write-media "tests/001-long-text_${i}.mp3" --write-subtitles "tests/001-long-text_${i}.srt" &
done
wait

# set return code to 0
ret=0

# compare files to make sure all are the same
for i in {b..z}
do
    cmp tests/001-long-text_a.srt "tests/001-long-text_${i}.srt" || ret=1
done

# exit with return code
exit "${ret}"


================================================
FILE: tests/001-long-text.txt
================================================

en.wikipedia.org
Mathematics
Contributors to Wikimedia projects
102–130 minutes

Mathematics is an area of knowledge that includes the topics of numbers, formulas and related structures, shapes and the spaces in which they are contained, and quantities and their changes. These topics are represented in modern mathematics with the major subdisciplines of number theory,[1] algebra,[2] geometry,[1] and analysis,[3] respectively. There is no general consensus among mathematicians about a common definition for their academic discipline.

Most mathematical activity involves the discovery of properties of abstract objects and the use of pure reason to prove them. These objects consist of either abstractions from nature or—in modern mathematics—entities that are stipulated to have certain properties, called axioms. A proof consists of a succession of applications of deductive rules to already established results. These results include previously proved theorems, axioms, and—in case of abstraction from nature—some basic properties that are considered true starting points of the theory under consideration.[4]

Mathematics is essential in the natural sciences, engineering, medicine, finance, computer science, and the social sciences. Although mathematics is extensively used for modeling phenomena, the fundamental truths of mathematics are independent from any scientific experimentation. Some areas of mathematics, such as statistics and game theory, are developed in close correlation with their applications and are often grouped under applied mathematics. Other areas are developed independently from any application (and are therefore called pure mathematics), but often later find practical applications.[5][6]

Historically, the concept of a proof and its associated mathematical rigour first appeared in Greek mathematics, most notably in Euclid's Elements.[7] Since its beginning, mathematics was primarily divided into geometry and arithmetic (the manipulation of natural numbers and fractions), until the 16th and 17th centuries, when algebra[a] and infinitesimal calculus were introduced as new fields. Since then, the interaction between mathematical innovations and scientific discoveries has led to a correlated increase in the development of both.[8] At the end of the 19th century, the foundational crisis of mathematics led to the systematization of the axiomatic method,[9] which heralded a dramatic increase in the number of mathematical areas and their fields of application. The contemporary Mathematics Subject Classification lists more than sixty first-level areas of mathematics.
Etymology

The word mathematics comes from Ancient Greek máthēma (μάθημα), meaning "that which is learnt",[10] "what one gets to know", hence also "study" and "science". The word came to have the narrower and more technical meaning of "mathematical study" even in Classical times.[b] Its adjective is mathēmatikós (μαθηματικός), meaning "related to learning" or "studious", which likewise further came to mean "mathematical".[14] In particular, mathēmatikḗ tékhnē (μαθηματικὴ τέχνη; Latin: ars mathematica) meant "the mathematical art".[10]

Similarly, one of the two main schools of thought in Pythagoreanism was known as the mathēmatikoi (μαθηματικοί)—which at the time meant "learners" rather than "mathematicians" in the modern sense. The Pythagoreans were likely the first to constrain the use of the word to just the study of arithmetic and geometry. By the time of Aristotle (384–322 BC) this meaning was fully established.[15]

In Latin, and in English until around 1700, the term mathematics more commonly meant "astrology" (or sometimes "astronomy") rather than "mathematics"; the meaning gradually changed to its present one from about 1500 to 1800. This change has resulted in several mistranslations: For example, Saint Augustine's warning that Christians should beware of mathematici, meaning "astrologers", is sometimes mistranslated as a condemnation of mathematicians.[16]

The apparent plural form in English goes back to the Latin neuter plural mathematica (Cicero), based on the Greek plural ta mathēmatiká (τὰ μαθηματικά) and means roughly "all things mathematical", although it is plausible that English borrowed only the adjective mathematic(al) and formed the noun mathematics anew, after the pattern of physics and metaphysics, inherited from Greek.[17] In English, the noun mathematics takes a singular verb. It is often shortened to maths[18] or, in North America, math.[19]
Areas of mathematics

Before the Renaissance, mathematics was divided into two main areas: arithmetic, regarding the manipulation of numbers, and geometry, regarding the study of shapes.[20] Some types of pseudoscience, such as numerology and astrology, were not then clearly distinguished from mathematics.[21]

During the Renaissance, two more areas appeared. Mathematical notation led to algebra which, roughly speaking, consists of the study and the manipulation of formulas. Calculus, consisting of the two subfields differential calculus and integral calculus, is the study of continuous functions, which model the typically nonlinear relationships between varying quantities, as represented by variables. This division into four main areas–arithmetic, geometry, algebra, calculus[22]–endured until the end of the 19th century. Areas such as celestial mechanics and solid mechanics were then studied by mathematicians, but now are considered as belonging to physics.[23] The subject of combinatorics has been studied for much of recorded history, yet did not become a separate branch of mathematics until the seventeenth century.[24]

At the end of the 19th century, the foundational crisis in mathematics and the resulting systematization of the axiomatic method led to an explosion of new areas of mathematics.[25][9] The 2020 Mathematics Subject Classification contains no less than sixty-three first-level areas.[26] Some of these areas correspond to the older division, as is true regarding number theory (the modern name for higher arithmetic) and geometry. Several other first-level areas have "geometry" in their names or are otherwise commonly considered part of geometry. Algebra and calculus do not appear as first-level areas but are respectively split into several first-level areas. Other first-level areas emerged during the 20th century or had not previously been considered as mathematics, such as mathematical logic and foundations.[27]
Number theory
This is the Ulam spiral, which illustrates the distribution of prime numbers. The dark diagonal lines in the spiral hint at the hypothesized approximate independence between being prime and being a value of a quadratic polynomial, a conjecture now known as Hardy and Littlewood's Conjecture F.

Number theory began with the manipulation of numbers, that is, natural numbers {\displaystyle (\mathbb {N} ),} and later expanded to integers {\displaystyle (\mathbb {Z} )} and rational numbers {\displaystyle (\mathbb {Q} ).} Number theory was once called arithmetic, but nowadays this term is mostly used for numerical calculations.[28] Number theory dates back to ancient Babylon and probably China. Two prominent early number theorists were Euclid of ancient Greece and Diophantus of Alexandria.[29] The modern study of number theory in its abstract form is largely attributed to Pierre de Fermat and Leonhard Euler. The field came to full fruition with the contributions of Adrien-Marie Legendre and Carl Friedrich Gauss.[30]

Many easily stated number problems have solutions that require sophisticated methods, often from across mathematics. A prominent example is Fermat's Last Theorem. This conjecture was stated in 1637 by Pierre de Fermat, but it was proved only in 1994 by Andrew Wiles, who used tools including scheme theory from algebraic geometry, category theory, and homological algebra.[31] Another example is Goldbach's conjecture, which asserts that every even integer greater than 2 is the sum of two prime numbers. Stated in 1742 by Christian Goldbach, it remains unproven despite considerable effort.[32]

Number theory includes several subareas, including analytic number theory, algebraic number theory, geometry of numbers (method oriented), diophantine equations, and transcendence theory (problem oriented).[27]
Geometry
On the surface of a sphere, Euclidean geometry only applies as a local approximation. For larger scales the sum of the angles of a triangle is not equal to 180°.

Geometry is one of the oldest branches of mathematics. It started with empirical recipes concerning shapes, such as lines, angles and circles, which were developed mainly for the needs of surveying and architecture, but has since blossomed out into many other subfields.[33]

A fundamental innovation was the ancient Greeks' introduction of the concept of proofs, which require that every assertion must be proved. For example, it is not sufficient to verify by measurement that, say, two lengths are equal; their equality must be proven via reasoning from previously accepted results (theorems) and a few basic statements. The basic statements are not subject to proof because they are self-evident (postulates), or are part of the definition of the subject of study (axioms). This principle, foundational for all mathematics, was first elaborated for geometry, and was systematized by Euclid around 300 BC in his book Elements.[34][35]

The resulting Euclidean geometry is the study of shapes and their arrangements constructed from lines, planes and circles in the Euclidean plane (plane geometry) and the three-dimensional Euclidean space.[c][33]

Euclidean geometry was developed without change of methods or scope until the 17th century, when René Descartes introduced what is now called Cartesian coordinates. This constituted a major change of paradigm: Instead of defining real numbers as lengths of line segments (see number line), it allowed the representation of points using their coordinates, which are numbers. Algebra (and later, calculus) can thus be used to solve geometrical problems. Geometry was split into two new subfields: synthetic geometry, which uses purely geometrical methods, and analytic geometry, which uses coordinates systemically.[36]

Analytic geometry allows the study of curves unrelated to circles and lines. Such curves can be defined as the graph of functions, the study of which led to differential geometry. They can also be defined as implicit equations, often polynomial equations (which spawned algebraic geometry). Analytic geometry also makes it possible to consider Euclidean spaces of higher than three dimensions.[33]

In the 19th century, mathematicians discovered non-Euclidean geometries, which do not follow the parallel postulate. By questioning that postulate's truth, this discovery has been viewed as joining Russell's paradox in revealing the foundational crisis of mathematics. This aspect of the crisis was solved by systematizing the axiomatic method, and adopting that the truth of the chosen axioms is not a mathematical problem.[37][9] In turn, the axiomatic method allows for the study of various geometries obtained either by changing the axioms or by considering properties that do not change under specific transformations of the space.[38]

Today's subareas of geometry include:[27]

    Projective geometry, introduced in the 16th century by Girard Desargues, extends Euclidean geometry by adding points at infinity at which parallel lines intersect. This simplifies many aspects of classical geometry by unifying the treatments for intersecting and parallel lines.
    Affine geometry, the study of properties relative to parallelism and independent from the concept of length.
    Differential geometry, the study of curves, surfaces, and their generalizations, which are defined using differentiable functions.
    Manifold theory, the study of shapes that are not necessarily embedded in a larger space.
    Riemannian geometry, the study of distance properties in curved spaces.
    Algebraic geometry, the study of curves, surfaces, and their generalizations, which are defined using polynomials.
    Topology, the study of properties that are kept under continuous deformations.
        Algebraic topology, the use in topology of algebraic methods, mainly homological algebra.
    Discrete geometry, the study of finite configurations in geometry.
    Convex geometry, the study of convex sets, which takes its importance from its applications in optimization.
    Complex geometry, the geometry obtained by replacing real numbers with complex numbers.

Algebra
The quadratic formula, which concisely expresses the solutions of all quadratic equations
The Rubik's Cube group is a concrete application of group theory.[39]

Algebra is the art of manipulating equations and formulas. Diophantus (3rd century) and al-Khwarizmi (9th century) were the two main precursors of algebra.[40][41] Diophantus solved some equations involving unknown natural numbers by deducing new relations until he obtained the solution. Al-Khwarizmi introduced systematic methods for transforming equations, such as moving a term from one side of an equation into the other side. The term algebra is derived from the Arabic word al-jabr meaning 'the reunion of broken parts'[42] that he used for naming one of these methods in the title of his main treatise.

Algebra became an area in its own right only with François Viète (1540–1603), who introduced the use of variables for representing unknown or unspecified numbers.[43] Variables allow mathematicians to describe the operations that have to be done on the numbers represented using mathematical formulas.

Until the 19th century, algebra consisted mainly of the study of linear equations (presently linear algebra), and polynomial equations in a single unknown, which were called algebraic equations (a term still in use, although it may be ambiguous). During the 19th century, mathematicians began to use variables to represent things other than numbers (such as matrices, modular integers, and geometric transformations), on which generalizations of arithmetic operations are often valid.[44] The concept of algebraic structure addresses this, consisting of a set whose elements are unspecified, of operations acting on the elements of the set, and rules that these operations must follow. The scope of algebra thus grew to include the study of algebraic structures. This object of algebra was called modern algebra or abstract algebra, as established by the influence and works of Emmy Noether.[45] (The latter term appears mainly in an educational context, in opposition to elementary algebra, which is concerned with the older way of manipulating formulas.)

Some types of algebraic structures have useful and often fundamental properties, in many areas of mathematics. Their study became autonomous parts of algebra, and include:[27]

    group theory;
    field theory;
    vector spaces, whose study is essentially the same as linear algebra;
    ring theory;
    commutative algebra, which is the study of commutative rings, includes the study of polynomials, and is a foundational part of algebraic geometry;
    homological algebra;
    Lie algebra and Lie group theory;
    Boolean algebra, which is widely used for the study of the logical structure of computers.

The study of types of algebraic structures as mathematical objects is the purpose of universal algebra and category theory.[46] The latter applies to every mathematical structure (not only algebraic ones). At its origin, it was introduced, together with homological algebra for allowing the algebraic study of non-algebraic objects such as topological spaces; this particular area of application is called algebraic topology.[47]
Calculus and analysis
A Cauchy sequence consists of elements such that all subsequent terms of a term become arbitrarily close to each other as the sequence progresses (from left to right).

Calculus, formerly called infinitesimal calculus, was introduced independently and simultaneously by 17th-century mathematicians Newton and Leibniz.[48] It is fundamentally the study of the relationship of variables that depend on each other. Calculus was expanded in the 18th century by Euler with the introduction of the concept of a function and many other results.[49] Presently, "calculus" refers mainly to the elementary part of this theory, and "analysis" is commonly used for advanced parts.

Analysis is further subdivided into real analysis, where variables represent real numbers, and complex analysis, where variables represent complex numbers. Analysis includes many subareas shared by other areas of mathematics which include:[27]

    Multivariable calculus
    Functional analysis, where variables represent varying functions;
    Integration, measure theory and potential theory, all strongly related with probability theory on a continuum;
    Ordinary differential equations;
    Partial differential equations;
    Numerical analysis, mainly devoted to the computation on computers of solutions of ordinary and partial differential equations that arise in many applications.

Discrete mathematics
A diagram representing a two-state Markov chain. The states are represented by 'A' and 'E'. The numbers are the probability of flipping the state.

Discrete mathematics, broadly speaking, is the study of individual, countable mathematical objects. An example is the set of all integers.[50] Because the objects of study here are discrete, the methods of calculus and mathematical analysis do not directly apply.[d] Algorithms—especially their implementation and computational complexity—play a major role in discrete mathematics.[51]

The four color theorem and optimal sphere packing were two major problems of discrete mathematics solved in the second half of the 20th century.[52] The P versus NP problem, which remains open to this day, is also important for discrete mathematics, since its solution would potentially impact a large number of computationally difficult problems.[53]

Discrete mathematics includes:[27]

    Combinatorics, the art of enumerating mathematical objects that satisfy some given constraints. Originally, these objects were elements or subsets of a given set; this has been extended to various objects, which establishes a strong link between combinatorics and other parts of discrete mathematics. For example, discrete geometry includes counting configurations of geometric shapes
    Graph theory and hypergraphs
    Coding theory, including error correcting codes and a part of cryptography
    Matroid theory
    Discrete geometry
    Discrete probability distributions
    Game theory (although continuous games are also studied, most common games, such as chess and poker are discrete)
    Discrete optimization, including combinatorial optimization, integer programming, constraint programming

Mathematical logic and set theory
The Venn diagram is a commonly used method to illustrate the relations between sets.

The two subjects of mathematical logic and set theory have belonged to mathematics since the end of the 19th century.[54][55] Before this period, sets were not considered to be mathematical objects, and logic, although used for mathematical proofs, belonged to philosophy and was not specifically studied by mathematicians.[56]

Before Cantor's study of infinite sets, mathematicians were reluctant to consider actually infinite collections, and considered infinity to be the result of endless enumeration. Cantor's work offended many mathematicians not only by considering actually infinite sets[57] but by showing that this implies different sizes of infinity, per Cantor's diagonal argument. This led to the controversy over Cantor's set theory.[58]

In the same period, various areas of mathematics concluded the former intuitive definitions of the basic mathematical objects were insufficient for ensuring mathematical rigour. Examples of such intuitive definitions are "a set is a collection of objects", "natural number is what is used for counting", "a point is a shape with a zero length in every direction", "a curve is a trace left by a moving point", etc.

This became the foundational crisis of mathematics.[59] It was eventually solved in mainstream mathematics by systematizing the axiomatic method inside a formalized set theory. Roughly speaking, each mathematical object is defined by the set of all similar objects and the properties that these objects must have.[25] For example, in Peano arithmetic, the natural numbers are defined by "zero is a number", "each number has a unique successor", "each number but zero has a unique predecessor", and some rules of reasoning.[60] This mathematical abstraction from reality is embodied in the modern philosophy of formalism, as founded by David Hilbert around 1910.[61]

The "nature" of the objects defined this way is a philosophical problem that mathematicians leave to philosophers, even if many mathematicians have opinions on this nature, and use their opinion—sometimes called "intuition"—to guide their study and proofs. The approach allows considering "logics" (that is, sets of allowed deducing rules), theorems, proofs, etc. as mathematical objects, and to prove theorems about them. For example, Gödel's incompleteness theorems assert, roughly speaking that, in every consistent formal system that contains the natural numbers, there are theorems that are true (that is provable in a stronger system), but not provable inside the system.[62] This approach to the foundations of mathematics was challenged during the first half of the 20th century by mathematicians led by Brouwer, who promoted intuitionistic logic, which explicitly lacks the law of excluded middle.[63][64]

These problems and debates led to a wide expansion of mathematical logic, with subareas such as model theory (modeling some logical theories inside other theories), proof theory, type theory, computability theory and computational complexity theory.[27] Although these aspects of mathematical logic were introduced before the rise of computers, their use in compiler design, program certification, proof assistants and other aspects of computer science, contributed in turn to the expansion of these logical theories.[65]
Statistics and other decision sciences
Whatever the form of a random population distribution (μ), the sampling mean (x̄) tends to a Gaussian distribution and its variance (σ) is given by the central limit theorem of probability theory.[66]

The field of statistics is a mathematical application that is employed for the collection and processing of data samples, using procedures based on mathematical methods especially probability theory. Statisticians generate data with random sampling or randomized experiments.[67] The design of a statistical sample or experiment determines the analytical methods that will be used. Analysis of data from observational studies is done using statistical models and the theory of inference, using model selection and estimation. The models and consequential predictions should then be tested against new data.[e]

Statistical theory studies decision problems such as minimizing the risk (expected loss) of a statistical action, such as using a procedure in, for example, parameter estimation, hypothesis testing, and selecting the best. In these traditional areas of mathematical statistics, a statistical-decision problem is formulated by minimizing an objective function, like expected loss or cost, under specific constraints. For example, designing a survey often involves minimizing the cost of estimating a population mean with a given level of confidence.[68] Because of its use of optimization, the mathematical theory of statistics overlaps with other decision sciences, such as operations research, control theory, and mathematical economics.[69]
Computational mathematics

Computational mathematics is the study of mathematical problems that are typically too large for human, numerical capacity.[70][71] Numerical analysis studies methods for problems in analysis using functional analysis and approximation theory; numerical analysis broadly includes the study of approximation and discretization with special focus on rounding errors.[72] Numerical analysis and, more broadly, scientific computing also study non-analytic topics of mathematical science, especially algorithmic-matrix-and-graph theory. Other areas of computational mathematics include computer algebra and symbolic computation.
History
Ancient

The history of mathematics is an ever-growing series of abstractions. Evolutionarily speaking, the first abstraction to ever be discovered, one shared by many animals,[73] was probably that of numbers: the realization that, for example, a collection of two apples and a collection of two oranges (say) have something in common, namely that there are two of them. As evidenced by tallies found on bone, in addition to recognizing how to count physical objects, prehistoric peoples may have also known how to count abstract quantities, like time—days, seasons, or years.[74][75]
The Babylonian mathematical tablet Plimpton 322, dated to 1800 BC

Evidence for more complex mathematics does not appear until around 3000 BC, when the Babylonians and Egyptians began using arithmetic, algebra, and geometry for taxation and other financial calculations, for building and construction, and for astronomy.[76] The oldest mathematical texts from Mesopotamia and Egypt are from 2000 to 1800 BC. Many early texts mention Pythagorean triples and so, by inference, the Pythagorean theorem seems to be the most ancient and widespread mathematical concept after basic arithmetic and geometry. It is in Babylonian mathematics that elementary arithmetic (addition, subtraction, multiplication, and division) first appear in the archaeological record. The Babylonians also possessed a place-value system and used a sexagesimal numeral system which is still in use today for measuring angles and time.[77]

In the 6th century BC, Greek mathematics began to emerge as a distinct discipline and some Ancient Greeks such as the Pythagoreans appeared to have considered it a subject in its own right.[78] Around 300 BC, Euclid organized mathematical knowledge by way of postulates and first principles, which evolved into the axiomatic method that is used in mathematics today, consisting of definition, axiom, theorem, and proof.[79] His book, Elements, is widely considered the most successful and influential textbook of all time.[80] The greatest mathematician of antiquity is often held to be Archimedes (c. 287 – c. 212 BC) of Syracuse.[81] He developed formulas for calculating the surface area and volume of solids of revolution and used the method of exhaustion to calculate the area under the arc of a parabola with the summation of an infinite series, in a manner not too dissimilar from modern calculus.[82] Other notable achievements of Greek mathematics are conic sections (Apollonius of Perga, 3rd century BC),[83] trigonometry (Hipparchus of Nicaea, 2nd century BC),[84] and the beginnings of algebra (Diophantus, 3rd century AD).[85]
The numerals used in the Bakhshali manuscript, dated between the 2nd century BC and the 2nd century AD

The Hindu–Arabic numeral system and the rules for the use of its operations, in use throughout the world today, evolved over the course of the first millennium AD in India and were transmitted to the Western world via Islamic mathematics.[86] Other notable developments of Indian mathematics include the modern definition and approximation of sine and cosine, and an early form of infinite series.[87][88]
Medieval and later
A page from al-Khwārizmī's Algebra

During the Golden Age of Islam, especially during the 9th and 10th centuries, mathematics saw many important innovations building on Greek mathematics. The most notable achievement of Islamic mathematics was the development of Algebra. Other achievements of the Islamic period include advances in spherical trigonometry and the addition of the decimal point to the Arabic numeral system.[89] Many notable mathematicians from this period were Persian, such as Al-Khwarismi, Omar Khayyam and Sharaf al-Dīn al-Ṭūsī.[90] The Greek and Arabic mathematical texts were in turn translated to Latin during the Middle Ages and made available in Europe.[91]

During the early modern period, mathematics began to develop at an accelerating pace in Western Europe, with innovations that revolutionized mathematics, such as the introduction of variables and symbolic notation by François Viète (1540–1603), the introduction of logarithms by John Napier in 1614, which greatly simplified numerical calculations, especially for astronomy and marine navigation, the introduction of coordinates by René Descartes (1596–1650) for reducing geometry to algebra, and the development of calculus by Isaac Newton (1642–1726/27) and Gottfried Leibniz (1646–1716). Leonhard Euler (1707–1783), the most notable mathematician of the 18th century, unified these innovations into a single corpus with a standardized terminology, and completed them with the discovery and the proof of numerous theorems.
Carl Friedrich Gauss

Perhaps the foremost mathematician of the 19th century was the German mathematician Carl Gauss, who made numerous contributions to fields such as algebra, analysis, differential geometry, matrix theory, number theory, and statistics.[92] In the early 20th century, Kurt Gödel transformed mathematics by publishing his incompleteness theorems, which show in part that any consistent axiomatic system—if powerful enough to describe arithmetic—will contain true propositions that cannot be proved.[62]

Mathematics has since been greatly extended, and there has been a fruitful interaction between mathematics and science, to the benefit of both. Mathematical discoveries continue to be made to this very day. According to Mikhail B. Sevryuk, in the January 2006 issue of the Bulletin of the American Mathematical Society, "The number of papers and books included in the Mathematical Reviews database since 1940 (the first year of operation of MR) is now more than 1.9 million, and more than 75 thousand items are added to the database each year. The overwhelming majority of works in this ocean contain new mathematical theorems and their proofs."[93]
Symbolic notation and terminology
An explanation of the sigma (Σ) summation notation

Mathematical notation is widely used in science and engineering for representing complex concepts and properties in a concise, unambiguous, and accurate way. This notation consists of symbols used for representing operations, unspecified numbers, relations and any other mathematical objects, and then assembling them into expressions and formulas.[94] More precisely, numbers and other mathematical objects are represented by symbols called variables, which are generally Latin or Greek letters, and often include subscripts. Operation and relations are generally represented by specific symbols or glyphs,[95] such as + (plus), × (multiplication), {\textstyle \int } (integral), = (equal), and < (less than).[96] All these symbols are generally grouped according to specific rules to form expressions and formulas.[97] Normally, expressions and formulas do not appear alone, but are included in sentences of the current language, where expressions play the role of noun phrases and formulas play the role of clauses.

Mathematics has developed a rich terminology covering a broad range of fields that study the properties of various abstract, idealized objects and how they interact. It is based on rigorous definitions that provide a standard foundation for communication. An axiom or postulate is a mathematical statement that is taken to be true without need of proof. If a mathematical statement has yet to be proven (or disproven), it is termed a conjecture. Through a series of rigorous arguments employing deductive reasoning, a statement that is proven to be true becomes a theorem. A specialized theorem that is mainly used to prove another theorem is called a lemma. A proven instance that forms part of a more general finding is termed a corollary.[98]

Numerous technical terms used in mathematics are neologisms, such as polynomial and homeomorphism.[99] Other technical terms are words of the common language that are used in an accurate meaning that may differ slightly from their common meaning. For example, in mathematics, "or" means "one, the other or both", while, in common language, it is either ambiguous or means "one or the other but not both" (in mathematics, the latter is called "exclusive or"). Finally, many mathematical terms are common words that are used with a completely different meaning.[100] This may lead to sentences that are correct and true mathematical assertions, but appear to be nonsense to people who do not have the required background. For example, "every free module is flat" and "a field is always a ring".
Relationship with sciences

Mathematics is used in most sciences for modeling phenomena, which then allows predictions to be made from experimental laws.[101] The independence of mathematical truth from any experimentation implies that the accuracy of such predictions depends only on the adequacy of the model.[102] Inaccurate predictions, rather than being caused by invalid mathematical concepts, imply the need to change the mathematical model used.[103] For example, the perihelion precession of Mercury could only be explained after the emergence of Einstein's general relativity, which replaced Newton's law of gravitation as a better mathematical model.[104]

There is still a philosophical debate whether mathematics is a science. However, in practice, mathematicians are typically grouped with scientists, and mathematics shares much in common with the physical sciences. Like them, it is falsifiable, which means in mathematics that, if a result or a theory is wrong, this can be proved by providing a counterexample. Similarly as in science, theories and results (theorems) are often obtained from experimentation.[105] In mathematics, the experimentation may consist of computation on selected examples or of the study of figures or other representations of mathematical objects (often mind representations without physical support). For example, when asked how he came about his theorems, Gauss once replied "durch planmässiges Tattonieren" (through systematic experimentation).[106] However, some authors emphasize that mathematics differs from the modern notion of science by not relying on empirical evidence.[107][108][109][110]
Pure and applied mathematics

Isaac Newton

Gottfried Wilhelm von Leibniz

Until the 19th century, the development of mathematics in the West was mainly motivated by the needs of technology and science, and there was no clear distinction between pure and applied mathematics.[111] For example, the natural numbers and arithmetic were introduced for the need of counting, and geometry was motivated by surveying, architecture and astronomy. Later, Isaac Newton introduced infinitesimal calculus for explaining the movement of the planets with his law of gravitation. Moreover, most mathematicians were also scientists, and many scientists were also mathematicians.[112] However, a notable exception occurred with the tradition of pure mathematics in Ancient Greece.[113] The problem of integer factorization, for example, which goes back to Euclid in 300 BC, had no practical application before its use in the RSA cryptosystem, now widely used for the security of computer networks.[114]

In the 19th century, mathematicians such as Karl Weierstrass and Richard Dedekind increasingly focused their research on internal problems, that is, pure mathematics.[111][115] This led to split mathematics into pure mathematics and applied mathematics, the latter being often considered as having a lower value among mathematical purists. However, the lines between the two are frequently blurred.[116]

The aftermath of World War II led to a surge in the development of applied mathematics in the US and elsewhere.[117][118] Many of the theories developed for applications were found interesting from the point of view of pure mathematics, and many results of pure mathematics were shown to have applications outside mathematics; in turn, the study of these applications may give new insights on the "pure theory".[119][120]

An example of the first case is the theory of distributions, introduced by Laurent Schwartz for validating computations done in quantum mechanics, which became immediately an important tool of (pure) mathematical analysis.[121] An example of the second case is the decidability of the first-order theory of the real numbers, a problem of pure mathematics that was proved true by Alfred Tarski, with an algorithm that is impossible to implement because of a computational complexity that is much too high.[122] For getting an algorithm that can be implemented and can solve systems of polynomial equations and inequalities, George Collins introduced the cylindrical algebraic decomposition that became a fundamental tool in real algebraic geometry.[123]

In the present day, the distinction between pure and applied mathematics is more a question of personal research aim of mathematicians than a division of mathematics into broad areas.[124][125] The Mathematics Subject Classification has a section for "general applied mathematics" but does not mention "pure mathematics".[27] However, these terms are still used in names of some university departments, such as at the Faculty of Mathematics at the University of Cambridge.
Unreasonable effectiveness

The unreasonable effectiveness of mathematics is a phenomenon that was named and first made explicit by physicist Eugene Wigner.[6] It is the fact that many mathematical theories (even the "purest") have applications outside their initial object. These applications may be completely outside their initial area of mathematics, and may concern physical phenomena that were completely unknown when the mathematical theory was introduced.[126] Examples of unexpected applications of mathematical theories can be found in many areas of mathematics.

A notable example is the prime factorization of natural numbers that was discovered more than 2,000 years before its common use for secure internet communications through the RSA cryptosystem.[127] A second historical example is the theory of ellipses. They were studied by the ancient Greek mathematicians as conic sections (that is, intersections of cones with planes). It is almost 2,000 years later that Johannes Kepler discovered that the trajectories of the planets are ellipses.[128]

In the 19th century, the internal development of geometry (pure mathematics) led to definition and study of non-Euclidean geometries, spaces of dimension higher than three and manifolds. At this time, these concepts seemed totally disconnected from the physical reality, but at the beginning of the 20th century, Albert Einstein developed the theory of relativity that uses fundamentally these concepts. In particular, spacetime of special relativity is a non-Euclidean space of dimension four, and spacetime of general relativity is a (curved) manifold of dimension four.[129][130]

A striking aspect of the interaction between mathematics and physics is when mathematics drives research in physics. This is illustrated by the discoveries of the positron and the baryon {\displaystyle \Omega ^{-}.} In both cases, the equations of the theories had unexplained solutions, which led to conjecture of the existence of an unknown particle, and the search for these particles. In both cases, these particles were discovered a few years later by specific experiments.[131][132][133]
Specific sciences
Physics
Diagram of a pendulum

Mathematics and physics have influenced each other over their modern history. Modern physics uses mathematics abundantly,[134] and is also the motivation of major mathematical developments.[135]
Computing

The rise of technology in the 20th century opened the way to a new science: computing.[f] This field is closely related to mathematics in several ways. Theoretical computer science is essentially mathematical in nature. Communication technologies apply branches of mathematics that may be very old (e.g., arithmetic), especially with respect to transmission security, in cryptography and coding theory. Discrete mathematics is useful in many areas of computer science, such as complexity theory, information theory, graph theory, and so on.[citation needed]

In return, computing has also become essential for obtaining new results. This is a group of techniques known as experimental mathematics, which is the use of experimentation to discover mathematical insights.[136] The most well-known example is the four-color theorem, which was proven in 1976 with the help of a computer. This revolutionized traditional mathematics, where the rule was that the mathematician should verify each part of the proof. In 1998, the Kepler conjecture on sphere packing seemed to also be partially proven by computer. An international team had since worked on writing a formal proof; it was finished (and verified) in 2015.[137]

Once written formally, a proof can be verified using a program called a proof assistant.[138] These programs are useful in situations where one is uncertain about a proof's correctness.[138]

A major open problem in theoretical computer science is P versus NP. It is one of the seven Millennium Prize Problems.[139]
Biology and chemistry
The skin of this giant pufferfish exhibits a Turing pattern, which can be modeled by reaction–diffusion systems.

Biology uses probability extensively – for example, in ecology or neurobiology.[140] Most of the discussion of probability in biology, however, centers on the concept of evolutionary fitness.[140]

Ecology heavily uses modeling to simulate population dynamics,[140][141] study ecosystems such as the predator-prey model, measure pollution diffusion,[142] or to assess climate change.[143] The dynamics of a population can be modeled by coupled differential equations, such as the Lotka–Volterra equations.[144] However, there is the problem of model validation. This is particularly acute when the results of modeling influence political decisions; the existence of contradictory models could allow nations to choose the most favorable model.[145]

Genotype evolution can be modeled with the Hardy-Weinberg principle.[citation needed]

Phylogeography uses probabilistic models.[citation needed]

Medicine uses statistical hypothesis testing, run on data from clinical trials, to determine whether a new treatment works.[citation needed]

Since the start of the 20th century, chemistry has used computing to model molecules in three dimensions. It turns out that the form of macromolecules in biology is variable and determines the action. Such modeling uses Euclidean geometry; neighboring atoms form a polyhedron whose distances and angles are fixed by the laws of interaction.[citation needed]
Earth sciences

Structural geology and climatology use probabilistic models to predict the risk of natural catastrophes.[citation needed] Similarly, meteorology, oceanography, and planetology also use mathematics due to their heavy use of models.[citation needed]

Areas of mathematics used in the social sciences include probability/statistics and differential equations. These are used in linguistics, economics, sociology,[146] and psychology.[147]
Supply and demand curves, like this one, are a staple of mathematical economics.

The fundamental postulate of mathematical economics is that of the rational individual actor – Homo economicus (lit. 'economic man').[148] In this model, the individual seeks to maximize their self-interest,[148] and always makes optimal choices using perfect information.[149][better source needed] This atomistic view of economics allows it to relatively easily mathematize its thinking, because individual calculations are transposed into mathematical calculations. Such mathematical modeling allows one to probe economic mechanisms which would be difficult to discover by a "literary" analysis.[citation needed] For example, explanations of economic cycles are not trivial. Without mathematical modeling, it is hard to go beyond statistical observations or unproven speculation.[citation needed]

However, many people have rejected or criticized the concept of Homo economicus.[149][better source needed] Economists note that real people have limited information, make poor choices and care about fairness, altruism, not just personal gain.[149][better source needed]

At the start of the 20th century, there was a development to express historical movements in formulas. In 1922, Nikolai Kondratiev discerned the ~50-year-long Kondratiev cycle, which explains phases of economic growth or crisis.[150] Towards the end of the 19th century, Nicolas-Remi Brück [fr] and Charles Henri Lagrange [fr] extended their analysis into geopolitics.[151] Peter Turchin has worked on developing cliodynamics since the 1990s.[152]

Even so, mathematization of the social sciences is not without danger. In the controversial book Fashionable Nonsense (1997), Sokal and Bricmont denounced the unfounded or abusive use of scientific terminology, particularly from mathematics or physics, in the social sciences.[153] The study of complex systems (evolution of unemployment, business capital, demographic evolution of a population, etc.) uses mathematical knowledge. However, the choice of counting criteria, particularly for unemployment, or of models, can be subject to controversy.[citation needed]
Relationship with astrology and esotericism

Some renowned mathematicians have also been considered to be renowned astrologists; for example, Ptolemy, Arab astronomers, Regiomantus, Cardano, Kepler, or John Dee. In the Middle Ages, astrology was considered a science that included mathematics. In his encyclopedia, Theodor Zwinger wrote that astrology was a mathematical science that studied the "active movement of bodies as they act on other bodies". He reserved to mathematics the need to "calculate with probability the influences [of stars]" to foresee their "conjunctions and oppositions".[154]

Astrology is no longer considered a science.[155]
Philosophy
Reality

The connection between mathematics and material reality has led to philosophical debates since at least the time of Pythagoras. The ancient philosopher Plato argued that abstractions that reflect material reality have themselves a reality that exists outside space and time. As a result, the philosophical view that mathematical objects somehow exist on their own in abstraction is often referred to as Platonism. Independently of their possible philosophical opinions, modern mathematicians may be generally considered as Platonists, since they think of and talk of their objects of study as real objects.[156]

Armand Borel summarized this view of mathematics reality as follows, and provided quotations of G. H. Hardy, Charles Hermite, Henri Poincaré and Albert Einstein that support his views.[131]

    Something becomes objective (as opposed to "subjective") as soon as we are convinced that it exists in the minds of others in the same form as it does in ours and that we can think about it and discuss it together.[157] Because the language of mathematics is so precise, it is ideally suited to defining concepts for which such a consensus exists. In my opinion, that is sufficient to provide us with a feeling of an objective existence, of a reality of mathematics ...

Nevertheless, Platonism and the concurrent views on abstraction do not explain the unreasonable effectiveness of mathematics.[158]
Proposed definitions

There is no general consensus about a definition of mathematics or its epistemological status—that is, its place among other human activities.[159][160] A great many professional mathematicians take no interest in a definition of mathematics, or consider it undefinable.[159] There is not even consensus on whether mathematics is an art or a science.[160] Some just say, "mathematics is what mathematicians do".[159] This makes sense, as there is a strong consensus among them about what is mathematics and what is not. Most proposed definitions try to define mathematics by its object of study.[161]

Aristotle defined mathematics as "the science of quantity" and this definition prevailed until the 18th century. However, Aristotle also noted a focus on quantity alone may not distinguish mathematics from sciences like physics; in his view, abstraction and studying quantity as a property "separable in thought" from real instances set mathe
Download .txt
gitextract_lm04wmz4/

├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── code-quality.yml
│       └── codeql-analysis.yml
├── .gitignore
├── .isort.cfg
├── LICENSE
├── README.md
├── build_and_publish.sh
├── examples/
│   ├── async_audio_gen_with_dynamic_voice_selection.py
│   ├── async_audio_gen_with_predefined_voice.py
│   ├── async_audio_streaming_with_predefined_voice_and_subtitles.py
│   ├── sync_audio_gen_with_predefined_voice.py
│   ├── sync_audio_streaming_with_predefined_voice_subtitles.py
│   └── sync_audio_streaming_with_predefined_voice_subtitles_print2stdout.py
├── format.sh
├── gpl-3.0.txt
├── lint.sh
├── mypy.ini
├── pylintrc
├── setup.cfg
├── setup.py
├── src/
│   ├── edge_playback/
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   ├── py.typed
│   │   ├── util.py
│   │   └── win32_playback.py
│   └── edge_tts/
│       ├── __init__.py
│       ├── __main__.py
│       ├── communicate.py
│       ├── constants.py
│       ├── data_classes.py
│       ├── drm.py
│       ├── exceptions.py
│       ├── py.typed
│       ├── srt_composer.py
│       ├── submaker.py
│       ├── typing.py
│       ├── util.py
│       ├── version.py
│       └── voices.py
└── tests/
    ├── 001-long-text.sh
    └── 001-long-text.txt
Download .txt
SYMBOL INDEX (85 symbols across 18 files)

FILE: examples/async_audio_gen_with_dynamic_voice_selection.py
  function amain (line 17) | async def amain() -> None:

FILE: examples/async_audio_gen_with_predefined_voice.py
  function amain (line 14) | async def amain() -> None:

FILE: examples/async_audio_streaming_with_predefined_voice_and_subtitles.py
  function amain (line 16) | async def amain() -> None:

FILE: examples/sync_audio_gen_with_predefined_voice.py
  function main (line 13) | def main() -> None:

FILE: examples/sync_audio_streaming_with_predefined_voice_subtitles.py
  function main (line 15) | def main() -> None:

FILE: examples/sync_audio_streaming_with_predefined_voice_subtitles_print2stdout.py
  function main (line 25) | def main() -> None:

FILE: src/edge_playback/__main__.py
  function _parse_args (line 14) | def _parse_args() -> Tuple[bool, List[str]]:
  function _check_deps (line 30) | def _check_deps(use_mpv: bool) -> None:
  function _create_temp_files (line 46) | def _create_temp_files(
  function _run_edge_tts (line 68) | def _run_edge_tts(
  function _play_media (line 79) | def _play_media(use_mpv: bool, mp3_fname: str, srt_fname: Optional[str])...
  function _cleanup (line 98) | def _cleanup(mp3_fname: Optional[str], srt_fname: Optional[str], keep: b...
  function _main (line 112) | def _main() -> None:

FILE: src/edge_playback/util.py
  function pr_err (line 6) | def pr_err(msg: str) -> None:

FILE: src/edge_playback/win32_playback.py
  function play_mp3_win32 (line 8) | def play_mp3_win32(mp3_fname: str) -> None:

FILE: src/edge_tts/communicate.py
  function get_headers_and_data (line 41) | def get_headers_and_data(
  function remove_incompatible_characters (line 65) | def remove_incompatible_characters(string: Union[str, bytes]) -> str:
  function connect_id (line 93) | def connect_id() -> str:
  function _find_last_newline_or_space_within_limit (line 103) | def _find_last_newline_or_space_within_limit(text: bytes, limit: int) ->...
  function _find_safe_utf8_split_point (line 127) | def _find_safe_utf8_split_point(text_segment: bytes) -> int:
  function _adjust_split_point_for_xml_entity (line 155) | def _adjust_split_point_for_xml_entity(text: bytes, split_at: int) -> int:
  function split_text_by_byte_length (line 185) | def split_text_by_byte_length(
  function mkssml (line 254) | def mkssml(tc: TTSConfig, escaped_text: Union[str, bytes]) -> str:
  function date_to_string (line 279) | def date_to_string() -> str:
  function ssml_headers_plus_data (line 295) | def ssml_headers_plus_data(request_id: str, timestamp: str, ssml: str) -...
  class Communicate (line 312) | class Communicate:
    method __init__ (line 318) | def __init__(
    method __parse_metadata (line 375) | def __parse_metadata(self, data: bytes) -> TTSChunk:
    method __stream (line 394) | async def __stream(self) -> AsyncGenerator[TTSChunk, None]:
    method stream (line 545) | async def stream(
    method save (line 576) | async def save(
    method stream_sync (line 600) | def stream_sync(self) -> Generator[TTSChunk, None, None]:
    method save_sync (line 625) | def save_sync(

FILE: src/edge_tts/data_classes.py
  class TTSConfig (line 13) | class TTSConfig:
    method validate_string_param (line 25) | def validate_string_param(param_name: str, param_value: str, pattern: ...
    method __post_init__ (line 43) | def __post_init__(self) -> None:
  class UtilArgs (line 79) | class UtilArgs(argparse.Namespace):

FILE: src/edge_tts/drm.py
  class DRM (line 20) | class DRM:
    method adj_clock_skew_seconds (line 28) | def adj_clock_skew_seconds(skew_seconds: float) -> None:
    method get_unix_timestamp (line 44) | def get_unix_timestamp() -> float:
    method parse_rfc2616_date (line 54) | def parse_rfc2616_date(date: str) -> Optional[float]:
    method handle_client_response_error (line 76) | def handle_client_response_error(e: aiohttp.ClientResponseError) -> None:
    method generate_sec_ms_gec (line 103) | def generate_sec_ms_gec() -> str:
    method generate_muid (line 137) | def generate_muid() -> str:
    method headers_with_muid (line 147) | def headers_with_muid(headers: Dict[str, str]) -> Dict[str, str]:

FILE: src/edge_tts/exceptions.py
  class EdgeTTSException (line 4) | class EdgeTTSException(Exception):
  class UnknownResponse (line 8) | class UnknownResponse(EdgeTTSException):
  class UnexpectedResponse (line 12) | class UnexpectedResponse(EdgeTTSException):
  class NoAudioReceived (line 19) | class NoAudioReceived(EdgeTTSException):
  class WebSocketError (line 23) | class WebSocketError(EdgeTTSException):
  class SkewAdjustmentError (line 27) | class SkewAdjustmentError(EdgeTTSException):

FILE: src/edge_tts/srt_composer.py
  class Subtitle (line 42) | class Subtitle:
    method __init__ (line 61) | def __init__(
    method __hash__ (line 69) | def __hash__(self) -> int:
    method __eq__ (line 72) | def __eq__(self, other: object) -> bool:
    method __lt__ (line 78) | def __lt__(self, other: object) -> bool:
    method __repr__ (line 88) | def __repr__(self) -> str:
    method to_srt (line 94) | def to_srt(self, eol: Union[str, None] = None) -> str:
  function make_legal_content (line 120) | def make_legal_content(content: str) -> str:
  function timedelta_to_srt_timestamp (line 147) | def timedelta_to_srt_timestamp(timedelta_timestamp: timedelta) -> str:
  function sort_and_reindex (line 171) | def sort_and_reindex(
  function _should_skip_sub (line 236) | def _should_skip_sub(subtitle: Subtitle) -> None:
  function compose (line 249) | def compose(
  class _ShouldSkipException (line 291) | class _ShouldSkipException(Exception):

FILE: src/edge_tts/submaker.py
  class SubMaker (line 10) | class SubMaker:
    method __init__ (line 15) | def __init__(self) -> None:
    method feed (line 19) | def feed(self, msg: TTSChunk) -> None:
    method get_srt (line 50) | def get_srt(self) -> str:
    method __str__ (line 59) | def __str__(self) -> str:

FILE: src/edge_tts/typing.py
  class TTSChunk (line 10) | class TTSChunk(TypedDict):
  class VoiceTag (line 20) | class VoiceTag(TypedDict):
  class Voice (line 27) | class Voice(TypedDict):
  class VoicesManagerVoice (line 40) | class VoicesManagerVoice(Voice):
  class VoicesManagerFind (line 46) | class VoicesManagerFind(TypedDict):
  class CommunicateState (line 54) | class CommunicateState(TypedDict):

FILE: src/edge_tts/util.py
  function _print_voices (line 16) | async def _print_voices(*, proxy: Optional[str]) -> None:
  function _run_tts (line 33) | async def _run_tts(args: UtilArgs) -> None:
  function amain (line 88) | async def amain() -> None:
  function main (line 139) | def main() -> None:

FILE: src/edge_tts/voices.py
  function __list_voices (line 17) | async def __list_voices(
  function list_voices (line 56) | async def list_voices(
  class VoicesManager (line 85) | class VoicesManager:
    method __init__ (line 90) | def __init__(self) -> None:
    method create (line 95) | async def create(
    method find (line 109) | def find(self, **kwargs: Unpack[VoicesManagerFind]) -> List[VoicesMana...
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (364K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 14,
    "preview": "github: rany2\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 106,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/code-quality.yml",
    "chars": 585,
    "preview": "name: \"Check code quality\"\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  l"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2270,
    "preview": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    # The branches below must be a subset of the "
  },
  {
    "path": ".gitignore",
    "chars": 3127,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".isort.cfg",
    "chars": 161,
    "preview": "[settings]\nmulti_line_output = 3\ninclude_trailing_comma = True\nforce_grid_wrap = 0\nuse_parentheses = True\nensure_newline"
  },
  {
    "path": "LICENSE",
    "chars": 9041,
    "preview": "The MIT license is used for 'src/edge_tts/srt_composer.py' only. All\nremaining files are licensed under the LGPLv3.\n\n---"
  },
  {
    "path": "README.md",
    "chars": 4526,
    "preview": "# edge-tts\n\n`edge-tts` is a Python module that allows you to use Microsoft Edge's online text-to-speech service from wit"
  },
  {
    "path": "build_and_publish.sh",
    "chars": 136,
    "preview": "#!/bin/sh\nset -eux\nrm -rf build dist src/*.egg-info\npython3 setup.py sdist bdist_wheel\ntwine check dist/*\ntwine upload d"
  },
  {
    "path": "examples/async_audio_gen_with_dynamic_voice_selection.py",
    "chars": 714,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Simple example to generate an audio file with randomized\ndynamic voice selection based on att"
  },
  {
    "path": "examples/async_audio_gen_with_predefined_voice.py",
    "chars": 407,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Simple example to generate audio with preset voice using async/await\"\"\"\n\nimport asyncio\n\nimpo"
  },
  {
    "path": "examples/async_audio_streaming_with_predefined_voice_and_subtitles.py",
    "chars": 850,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Example showing how to use use .stream() method to get audio chunks\nand feed them to SubMaker"
  },
  {
    "path": "examples/sync_audio_gen_with_predefined_voice.py",
    "chars": 374,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Sync variant of the example for generating audio with a predefined voice\"\"\"\n\n\nimport edge_tts"
  },
  {
    "path": "examples/sync_audio_streaming_with_predefined_voice_subtitles.py",
    "chars": 807,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Sync variant of the async .stream() method to\nget audio chunks and feed them to SubMaker to\ng"
  },
  {
    "path": "examples/sync_audio_streaming_with_predefined_voice_subtitles_print2stdout.py",
    "chars": 1003,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Sync variant of the async .stream() method to\nget audio chunks and feed them to SubMaker to\ng"
  },
  {
    "path": "format.sh",
    "chars": 99,
    "preview": "set -eux\nfind src examples -name '*.py' | xargs black\nfind src examples -name '*.py' | xargs isort\n"
  },
  {
    "path": "gpl-3.0.txt",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "lint.sh",
    "chars": 108,
    "preview": "#!/bin/sh\nset -ux\nfind src examples -name '*.py' | xargs pylint\nfind src examples -name '*.py' | xargs mypy\n"
  },
  {
    "path": "mypy.ini",
    "chars": 501,
    "preview": "[mypy]\ndisallow_any_unimported = True\ndisallow_any_expr = False\ndisallow_any_decorated = True\ndisallow_any_explicit = Fa"
  },
  {
    "path": "pylintrc",
    "chars": 21231,
    "preview": "[MAIN]\n\n# Analyse import fallback blocks. This can be used to support both Python 2 and\n# 3 compatible code, which means"
  },
  {
    "path": "setup.cfg",
    "chars": 912,
    "preview": "[metadata]\nname = edge-tts\nversion = attr: edge_tts.version.__version__\nauthor = rany\nauthor_email = ranygh@riseup.net\nd"
  },
  {
    "path": "setup.py",
    "chars": 307,
    "preview": "from setuptools import setup\n\n# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.\nsetup(\n    nam"
  },
  {
    "path": "src/edge_playback/__init__.py",
    "chars": 226,
    "preview": "\"\"\"The edge_playback package wraps the functionality of mpv and edge-tts to generate\ntext-to-speech (TTS) using edge-tts"
  },
  {
    "path": "src/edge_playback/__main__.py",
    "chars": 3832,
    "preview": "\"\"\"Main entrypoint for the edge-playback package.\"\"\"\n\nimport argparse\nimport os\nimport subprocess\nimport sys\nimport temp"
  },
  {
    "path": "src/edge_playback/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/edge_playback/util.py",
    "chars": 145,
    "preview": "\"\"\"Utility functions for edge-playback\"\"\"\n\nimport sys\n\n\ndef pr_err(msg: str) -> None:\n    \"\"\"Print to stderr.\"\"\"\n    pri"
  },
  {
    "path": "src/edge_playback/win32_playback.py",
    "chars": 1690,
    "preview": "\"\"\"Functions to play audio on Windows using native win32 APIs\"\"\"\n\nimport sys\n\nfrom .util import pr_err\n\n\ndef play_mp3_wi"
  },
  {
    "path": "src/edge_tts/__init__.py",
    "chars": 468,
    "preview": "\"\"\"edge-tts allows you to use Microsoft Edge's online text-to-speech service without\nneeding Windows or the Edge browser"
  },
  {
    "path": "src/edge_tts/__main__.py",
    "chars": 111,
    "preview": "\"\"\"Main entrypoint for the edge-tts package.\"\"\"\n\nfrom .util import main\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/edge_tts/communicate.py",
    "chars": 23422,
    "preview": "\"\"\"Communicate with the service. Only the Communicate class should be used by\nend-users. The other classes and functions"
  },
  {
    "path": "src/edge_tts/constants.py",
    "chars": 1468,
    "preview": "\"\"\"Constants for the edge_tts package.\"\"\"\n\nBASE_URL = \"speech.platform.bing.com/consumer/speech/synthesize/readaloud\"\nTR"
  },
  {
    "path": "src/edge_tts/data_classes.py",
    "chars": 2793,
    "preview": "\"\"\"Data models for edge-tts.\"\"\"\n\n# pylint: disable=too-few-public-methods\n\nimport argparse\nimport re\nfrom dataclasses im"
  },
  {
    "path": "src/edge_tts/drm.py",
    "chars": 5291,
    "preview": "\"\"\"DRM module is used to handle DRM operations with clock skew correction.\nCurrently the only DRM operation is generatin"
  },
  {
    "path": "src/edge_tts/exceptions.py",
    "chars": 798,
    "preview": "\"\"\"Custom exceptions for the edge-tts package.\"\"\"\n\n\nclass EdgeTTSException(Exception):\n    \"\"\"Base exception for the edg"
  },
  {
    "path": "src/edge_tts/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/edge_tts/srt_composer.py",
    "chars": 10155,
    "preview": "\"\"\"A tiny library for composing SRT files.\n\nBased on https://github.com/cdown/srt with parsing, subtitle modifying\nfunct"
  },
  {
    "path": "src/edge_tts/submaker.py",
    "chars": 1757,
    "preview": "\"\"\"SubMaker module is used to generate subtitles from WordBoundary and SentenceBoundary events.\"\"\"\n\nfrom datetime import"
  },
  {
    "path": "src/edge_tts/typing.py",
    "chars": 1414,
    "preview": "\"\"\"Custom types for edge-tts.\"\"\"\n\n# pylint: disable=too-few-public-methods\n\nfrom typing import List\n\nfrom typing_extensi"
  },
  {
    "path": "src/edge_tts/util.py",
    "chars": 4699,
    "preview": "\"\"\"Utility functions for the command line interface. Used by the main module.\"\"\"\n\nimport argparse\nimport asyncio\nimport "
  },
  {
    "path": "src/edge_tts/version.py",
    "chars": 144,
    "preview": "\"\"\"Version information for the edge_tts package.\"\"\"\n\n__version__ = \"7.2.7\"\n__version_info__ = tuple(int(num) for num in "
  },
  {
    "path": "src/edge_tts/voices.py",
    "chars": 3999,
    "preview": "\"\"\"This module contains functions to list all available voices and a class to find the\ncorrect voice based on their attr"
  },
  {
    "path": "tests/001-long-text.sh",
    "chars": 524,
    "preview": "#!/usr/bin/env bash\n\n# test if prompt file exists\nif ! [[ -f \"tests/001-long-text.txt\" ]]\nthen\n    echo \"File not found!"
  },
  {
    "path": "tests/001-long-text.txt",
    "chars": 208485,
    "preview": "\nen.wikipedia.org\nMathematics\nContributors to Wikimedia projects\n102–130 minutes\n\nMathematics is an area of knowledge th"
  }
]

About this extraction

This page contains the full source code of the rany2/edge-tts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (345.6 KB), approximately 86.5k tokens, and a symbol index with 85 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!