Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 33 additions & 25 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Build

on:
Expand All @@ -10,36 +7,47 @@ on:
branches: [ "main" ]

permissions:
contents:
write
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v3
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10"
- name: Install dependencies
python-version: "3.12"
- name: Install system dependencies
run: |
python -m pip install --upgrade pip
sudo apt-get update
sudo apt-get install libcairo2-dev libpango1.0-dev ffmpeg
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Install the package
sudo apt-get install libcairo2-dev libpango1.0-dev ffmpeg portaudio19-dev
- name: Install Python dependencies
run: uv sync --dev --frozen
- name: Add virtual environment tools to PATH
run: echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
- name: Check formatting
run: uv run ruff format --check .
- name: Lint
run: uv run ruff check .
- name: Typecheck
run: uv run ty check manim_voiceover
- name: Mypy
run: uv run mypy
- name: Test with coverage
run: uv run pytest --cov=manim_voiceover --cov-fail-under=85
- name: Check duplication
run: uvx slophammer-py@0.3.0 dry .
- name: Check mutations
run: |
pip install .
- name: Test with pytest
uv run mutmut run --max-children 2
uv run python scripts/check_mutmut_results.py
rm -rf mutants
- name: Audit dependencies
run: uv run pip-audit
- name: Run Slophammer
run: |
pytest
uvx slophammer-py@0.3.0 check .
uvx slophammer-py@0.3.0 check . --execute
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# AGENTS.md

These instructions apply to this repository.

- Keep generated changes small and reviewable by maintainers.
- When applying Slophammer standards, follow the upstream agent entrypoint:
https://github.com/osolmaz/slophammer/blob/main/docs/AGENT_ENTRYPOINT.md
- Preserve the public package API unless the change is explicitly scoped as breaking.
- Add or update tests when changing behavior.
- Run `uv run ruff format --check .`, `uv run ruff check .`, `uv run ty check manim_voiceover`, `uv run mypy`, `uv run pytest --cov=manim_voiceover --cov-fail-under=85`, `uvx slophammer-py@0.3.0 dry .`, `uv run mutmut run`, `uv run python scripts/check_mutmut_results.py`, `uv run pip-audit`, `uvx slophammer-py@0.3.0 check .`, and `uvx slophammer-py@0.3.0 check . --execute` before submitting a change.
- Public functions and meaningful helpers must stay annotated and pass Ruff `ANN`, ty, and strict mypy.
- Keep `Any`, casts, and import ignores isolated to external-library boundaries with a clear reason.
- Use the existing project style and avoid unrelated formatting churn.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ https://user-images.githubusercontent.com/2453968/198145393-6a1bd709-4441-4821-8
Currently supported TTS services (aside from the CLI that allows you to records your own voice):

- [Azure Text to Speech](https://azure.microsoft.com/en-us/services/cognitive-services/text-to-speech/) (Recommended for AI voices)
- [Coqui TTS](https://github.com/coqui-ai/TTS/)
- [gTTS](https://github.com/pndurette/gTTS/)
- [pyttsx3](https://github.com/nateshmbhat/pyttsx3)

Expand All @@ -43,4 +42,4 @@ Currently supported TTS services (aside from the CLI that allows you to records

## Translate

Manim Voiceover can use machine translation services like [DeepL](https://www.deepl.com/) to translate voiceovers into other languages. [Check out the docs for more details.](https://voiceover.manim.community/en/latest/translate.html)
Manim Voiceover can use machine translation services like [DeepL](https://www.deepl.com/) to translate voiceovers into other languages. [Check out the docs for more details.](https://voiceover.manim.community/en/latest/translate.html)
4 changes: 0 additions & 4 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ Speech services
:members:
:show-inheritance:

.. automodule:: manim_voiceover.services.coqui
:members:
:show-inheritance:

.. automodule:: manim_voiceover.services.gtts
:members:
:show-inheritance:
Expand Down
23 changes: 0 additions & 23 deletions docs/source/services.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ Manim Voiceover defines the :py:class:`~~base.SpeechService` class for adding ne
- No
- Yes
- Requires ElevenLabs account. Click `here <https://elevenlabs.io/sign-up>`__ to sign up.
* - :py:class:`~coqui.CoquiService`
- Good, human-like
- Yes
- No
- Requires `PyTorch <https://pytorch.org/>`__ to run. May be difficult to set up on certain platforms.
* - :py:class:`~gtts.GTTSService`
- Good
- No
Expand Down Expand Up @@ -109,24 +104,6 @@ for more details.

Refer to the `example usage <https://github.com/ManimCommunity/manim-voiceover/blob/main/examples/azure-example.py>`__ to get started.

:py:class:`~coqui.CoquiService`
*******************************

`Coqui TTS <https://tts.readthedocs.io/>`__ is an open source neural text-to-speech engine.
It is a fork of Mozilla TTS, which is an implementation of Tacotron 2.
It is a very good TTS engine that produces human-like speech.
However, it requires `PyTorch <https://pytorch.org/>`__ to run, which may be difficult to set up on certain platforms.

Install Manim Voiceover with the ``coqui`` extra in order to use :py:class:`~coqui.CoquiService`:

.. code:: sh

pip install "manim-voiceover[coqui]"

If you run into issues with PyTorch or NumPy, try changing your Python version to 3.9.

Refer to the `example usage <https://github.com/ManimCommunity/manim-voiceover/blob/main/examples/coqui-example.py>`__ to get started.

:py:class:`~gtts.GTTSService`
*****************************

Expand Down
2 changes: 0 additions & 2 deletions examples/bookmark-example.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from manim import *
from manim_voiceover import VoiceoverScene

# from manim_voiceover.services.coqui import CoquiService
from manim_voiceover.services.azure import AzureService


class BookmarkExample(VoiceoverScene):
def construct(self):
# self.set_speech_service(CoquiService(transcription_model='base'))
self.set_speech_service(
AzureService(
voice="en-US-AriaNeural",
Expand Down
30 changes: 0 additions & 30 deletions examples/coqui-example.py

This file was deleted.

6 changes: 3 additions & 3 deletions manim_voiceover/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from manim_voiceover.tracker import VoiceoverTracker
from manim_voiceover.voiceover_scene import VoiceoverScene

from importlib.metadata import PackageNotFoundError, version

from manim_voiceover.tracker import VoiceoverTracker as VoiceoverTracker
from manim_voiceover.voiceover_scene import VoiceoverScene as VoiceoverScene

try:
__version__: str = version("manim-voiceover")
except PackageNotFoundError:
Expand Down
57 changes: 57 additions & 0 deletions manim_voiceover/_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import Dict, List, Mapping, TypedDict, Union

JsonScalar = Union[str, int, float, bool, None]
JsonValue = Union[JsonScalar, Dict[str, "JsonValue"], List["JsonValue"]]


def json_value(value: object) -> JsonValue:
if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, list):
return [json_value(item) for item in value]
if isinstance(value, dict):
output: Dict[str, JsonValue] = {}
for key, item in value.items():
if not isinstance(key, str):
raise TypeError("JSON object keys must be strings")
output[key] = json_value(item)
return output
raise TypeError("value must be JSON-compatible")


def json_object(value: Mapping[object, object]) -> Dict[str, JsonValue]:
output: Dict[str, JsonValue] = {}
for key, item in value.items():
if not isinstance(key, str):
raise TypeError("JSON object keys must be strings")
output[key] = json_value(item)
return output


class WordTimestamp(TypedDict):
word: str
start: float


class TranscriptionSegment(TypedDict):
words: List[WordTimestamp]


class WordBoundary(TypedDict, total=False):
audio_offset: int
duration_milliseconds: int
text_offset: int
word_length: int
text: str
boundary_type: str


class VoiceoverData(TypedDict, total=False):
input_text: str
input_data: Mapping[str, JsonValue]
ssml: str
word_boundaries: List[WordBoundary]
original_audio: str
final_audio: str
json_path: str
transcribed_text: str
2 changes: 0 additions & 2 deletions manim_voiceover/defaults.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from pathlib import Path

DEFAULT_VOICEOVER_CACHE_DIR = "voiceovers"
DEFAULT_VOICEOVER_CACHE_JSON_FILENAME = "cache.json"

Expand Down
Loading