Skip to content

Commit 2a22eac

Browse files
committed
style: modernize typing imports
1 parent d6ee3bb commit 2a22eac

21 files changed

Lines changed: 189 additions & 197 deletions

src/manim_voiceover/_typing.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import Dict, List, Mapping, TypedDict, Union
1+
import typing as t
2+
from collections.abc import Mapping
23

3-
JsonScalar = Union[str, int, float, bool, None]
4-
JsonValue = Union[JsonScalar, Dict[str, "JsonValue"], List["JsonValue"]]
4+
JsonScalar = str | int | float | bool | None
5+
JsonValue = JsonScalar | dict[str, "JsonValue"] | list["JsonValue"]
56

67

78
def json_value(value: object) -> JsonValue:
@@ -10,7 +11,7 @@ def json_value(value: object) -> JsonValue:
1011
if isinstance(value, list):
1112
return [json_value(item) for item in value]
1213
if isinstance(value, dict):
13-
output: Dict[str, JsonValue] = {}
14+
output: dict[str, JsonValue] = {}
1415
for key, item in value.items():
1516
if not isinstance(key, str):
1617
raise TypeError("JSON object keys must be strings")
@@ -19,25 +20,25 @@ def json_value(value: object) -> JsonValue:
1920
raise TypeError("value must be JSON-compatible")
2021

2122

22-
def json_object(value: Mapping[str, object]) -> Dict[str, JsonValue]:
23-
output: Dict[str, JsonValue] = {}
23+
def json_object(value: Mapping[str, object]) -> dict[str, JsonValue]:
24+
output: dict[str, JsonValue] = {}
2425
for key, item in value.items():
2526
if not isinstance(key, str):
2627
raise TypeError("JSON object keys must be strings")
2728
output[key] = json_value(item)
2829
return output
2930

3031

31-
class WordTimestamp(TypedDict):
32+
class WordTimestamp(t.TypedDict):
3233
word: str
3334
start: float
3435

3536

36-
class TranscriptionSegment(TypedDict):
37-
words: List[WordTimestamp]
37+
class TranscriptionSegment(t.TypedDict):
38+
words: list[WordTimestamp]
3839

3940

40-
class WordBoundary(TypedDict, total=False):
41+
class WordBoundary(t.TypedDict, total=False):
4142
audio_offset: int
4243
duration_milliseconds: int
4344
text_offset: int
@@ -46,11 +47,11 @@ class WordBoundary(TypedDict, total=False):
4647
boundary_type: str
4748

4849

49-
class VoiceoverData(TypedDict, total=False):
50+
class VoiceoverData(t.TypedDict, total=False):
5051
input_text: str
5152
input_data: Mapping[str, JsonValue]
5253
ssml: str
53-
word_boundaries: List[WordBoundary]
54+
word_boundaries: list[WordBoundary]
5455
original_audio: str
5556
final_audio: str
5657
json_path: str

src/manim_voiceover/helper.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@
44
import re
55
import sys
66
import textwrap
7+
import typing as t
8+
from collections.abc import Iterator, Mapping, Sequence
79
from pathlib import Path
8-
from typing import TYPE_CHECKING, Iterator, List, Mapping, Optional, Sequence, TypeVar, Union
910

1011
import pip
1112
from manim import logger
1213
from pydub import AudioSegment
1314

1415
from manim_voiceover._typing import JsonValue, VoiceoverData
1516

16-
T = TypeVar("T")
17-
if TYPE_CHECKING:
18-
PathLike = Union[str, os.PathLike[str]]
19-
else:
20-
PathLike = Union[str, os.PathLike]
17+
T = t.TypeVar("T")
18+
PathLike = str | os.PathLike[str]
2119

2220

2321
def chunks(lst: Sequence[T], n: int) -> Iterator[Sequence[T]]:
@@ -32,7 +30,7 @@ def remove_bookmarks(input: str) -> str:
3230

3331
def wav2mp3(
3432
wav_path: PathLike,
35-
mp3_path: Optional[PathLike] = None,
33+
mp3_path: PathLike | None = None,
3634
remove_wav: bool = True,
3735
bitrate: str = "312k",
3836
) -> None:
@@ -50,7 +48,7 @@ def wav2mp3(
5048
logger.info(f"Saved {mp3_path}")
5149

5250

53-
def msg_box(msg: str, indent: int = 1, width: Optional[int] = None, title: Optional[str] = None) -> str:
51+
def msg_box(msg: str, indent: int = 1, width: int | None = None, title: str | None = None) -> str:
5452
"""Print message-box with optional title."""
5553
raw_lines = msg.splitlines() or [""]
5654
space = " " * indent
@@ -108,7 +106,7 @@ def trim_silence(
108106
return trimmed_sound
109107

110108

111-
def append_to_json_file(json_file: PathLike, data: Union[Mapping[str, JsonValue], VoiceoverData]) -> None:
109+
def append_to_json_file(json_file: PathLike, data: Mapping[str, JsonValue] | VoiceoverData) -> None:
112110
"""Append data to json file"""
113111
json_path = Path(json_file)
114112
if not json_path.exists():
@@ -142,7 +140,7 @@ def prompt_ask_missing_package(target_module: str, package_name: str) -> None:
142140

143141

144142
def prompt_ask_missing_extras(
145-
target_module: Union[str, List[str]],
143+
target_module: str | list[str],
146144
extras: str,
147145
dependent_item: str,
148146
) -> None:

src/manim_voiceover/modify_audio.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import os
2+
import typing as t
23
import uuid
34
from pathlib import Path
4-
from typing import Optional, Protocol, Union
55

66
import sox
77
from mutagen.mp3 import MP3
88
from mutagen.wave import WAVE
99

10-
PathLike = Union[str, Path]
10+
PathLike = str | Path
1111

1212

13-
class _AudioInfo(Protocol):
13+
class _AudioInfo(t.Protocol):
1414
length: float
1515

1616

17-
class _AudioFile(Protocol):
18-
info: Optional[_AudioInfo]
17+
class _AudioFile(t.Protocol):
18+
info: _AudioInfo | None
1919

2020

2121
def _read_wave(path: PathLike) -> _AudioFile:

src/manim_voiceover/services/azure.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22
import sys
3+
import typing as t
4+
from collections.abc import Mapping
35
from pathlib import Path
4-
from typing import Dict, List, Mapping, Optional, Protocol, Tuple
56

67
from dotenv import find_dotenv, load_dotenv
78
from manim import logger
@@ -23,12 +24,12 @@
2324
load_dotenv(find_dotenv(usecwd=True))
2425

2526

26-
class CancellationDetailsProtocol(Protocol):
27+
class CancellationDetailsProtocol(t.Protocol):
2728
reason: object
28-
error_details: Optional[str]
29+
error_details: str | None
2930

3031

31-
class SpeechSynthesisResultProtocol(Protocol):
32+
class SpeechSynthesisResultProtocol(t.Protocol):
3233
reason: object
3334
cancellation_details: CancellationDetailsProtocol
3435

@@ -51,15 +52,15 @@ def _json_value(value: object) -> JsonValue:
5152
return json_value(value)
5253

5354

54-
def _normalize_prosody(value: object) -> Optional[Dict[str, JsonValue]]:
55+
def _normalize_prosody(value: object) -> dict[str, JsonValue] | None:
5556
if value is None:
5657
return None
5758
if not isinstance(value, dict):
5859
raise ValueError(
5960
"The prosody argument must be a dict that contains at least one of the following keys: "
6061
"'pitch', 'contour', 'range', 'rate', 'volume'."
6162
)
62-
prosody: Dict[str, JsonValue] = {}
63+
prosody: dict[str, JsonValue] = {}
6364
for key, item in value.items():
6465
if not isinstance(key, str):
6566
raise TypeError("prosody must map string keys to JSON-compatible values")
@@ -98,7 +99,7 @@ def create_dotenv_azure() -> None:
9899
sys.exit()
99100

100101

101-
def _get_azure_credentials() -> Tuple[str, str]:
102+
def _get_azure_credentials() -> tuple[str, str]:
102103
try:
103104
return os.environ["AZURE_SUBSCRIPTION_KEY"], os.environ["AZURE_SERVICE_REGION"]
104105
except KeyError:
@@ -118,9 +119,9 @@ def __init__(
118119
self,
119120
voice: str = "en-US-AriaNeural",
120121
# style="newscast-casual",
121-
style: Optional[str] = None,
122+
style: str | None = None,
122123
output_format: str = "Audio48Khz192KBitRateMonoMp3",
123-
prosody: Optional[Dict[str, JsonValue]] = None,
124+
prosody: dict[str, JsonValue] | None = None,
124125
**kwargs: object,
125126
) -> None:
126127
"""
@@ -139,7 +140,7 @@ def __init__(
139140
self.prosody = prosody
140141
initialize_speech_service(self, kwargs)
141142

142-
def _build_ssml(self, text: str, prosody: Optional[Dict[str, JsonValue]]) -> Tuple[str, int]:
143+
def _build_ssml(self, text: str, prosody: dict[str, JsonValue] | None) -> tuple[str, int]:
143144
ssml_beginning = (
144145
'<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" '
145146
'xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="en-US">'
@@ -180,8 +181,8 @@ def _raise_for_canceled_synthesis(self, speech_synthesis_result: SpeechSynthesis
180181
def generate_from_text(
181182
self,
182183
text: str,
183-
cache_dir: Optional[PathLike] = None,
184-
path: Optional[PathLike] = None,
184+
cache_dir: PathLike | None = None,
185+
path: PathLike | None = None,
185186
**kwargs: object,
186187
) -> VoiceoverData:
187188
""""""
@@ -195,7 +196,7 @@ def generate_from_text(
195196
prosody = _normalize_prosody(kwargs.get("prosody", self.prosody))
196197
ssml, initial_offset = self._build_ssml(inner, prosody)
197198

198-
input_data: Dict[str, JsonValue] = {
199+
input_data: dict[str, JsonValue] = {
199200
"input_text": text,
200201
"ssml": ssml,
201202
"service": "azure",
@@ -226,7 +227,7 @@ def generate_from_text(
226227
audio_config = speechsdk.audio.AudioOutputConfig(filename=str(Path(cache_dir) / audio_path))
227228

228229
speech_service = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
229-
word_boundaries: List[Mapping[str, object]] = []
230+
word_boundaries: list[Mapping[str, object]] = []
230231
# speech_synthesizer.bookmark_reached.connect(lambda evt: print(
231232
# "Bookmark reached: {}, audio offset: {}ms, bookmark text: {}.".format(evt, evt.audio_offset, evt.text)))
232233

src/manim_voiceover/services/base.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,20 @@
2525
)
2626
from manim_voiceover.tracker import AUDIO_OFFSET_RESOLUTION
2727

28-
if t.TYPE_CHECKING:
29-
PathLike = t.Union[str, os.PathLike[str]]
30-
else:
31-
PathLike = t.Union[str, os.PathLike]
28+
PathLike = str | os.PathLike[str]
3229

3330

3431
class TranscriptionResult(t.Protocol):
3532
text: str
3633

37-
def segments_to_dicts(self) -> t.List[TranscriptionSegment]: ...
34+
def segments_to_dicts(self) -> list[TranscriptionSegment]: ...
3835

3936

4037
class WhisperModel(t.Protocol):
4138
def transcribe(self, audio_path: str, **kwargs: object) -> TranscriptionResult: ...
4239

4340

44-
def _pop_optional_path(kwargs: t.MutableMapping[str, object], key: str) -> t.Optional[PathLike]:
41+
def _pop_optional_path(kwargs: t.MutableMapping[str, object], key: str) -> PathLike | None:
4542
value = kwargs.pop(key, None)
4643
if value is None:
4744
return None
@@ -59,7 +56,7 @@ def path_to_string(path: PathLike) -> str:
5956
raise TypeError("path must resolve to a string path")
6057

6158

62-
def _pop_optional_str(kwargs: t.MutableMapping[str, object], key: str) -> t.Optional[str]:
59+
def _pop_optional_str(kwargs: t.MutableMapping[str, object], key: str) -> str | None:
6360
value = kwargs.pop(key, None)
6461
if value is None or isinstance(value, str):
6562
return value
@@ -73,7 +70,7 @@ def _pop_float(kwargs: t.MutableMapping[str, object], key: str, default: float)
7370
raise TypeError(f"{key} must be a number")
7471

7572

76-
def _pop_optional_dict(kwargs: t.MutableMapping[str, object], key: str) -> t.Optional[t.Dict[str, object]]:
73+
def _pop_optional_dict(kwargs: t.MutableMapping[str, object], key: str) -> dict[str, object] | None:
7774
value = kwargs.pop(key, None)
7875
if value is None:
7976
return None
@@ -85,7 +82,7 @@ def _pop_optional_dict(kwargs: t.MutableMapping[str, object], key: str) -> t.Opt
8582
def initialize_speech_service(
8683
service: "SpeechService",
8784
kwargs: t.MutableMapping[str, object],
88-
transcription_model: t.Optional[str] = None,
85+
transcription_model: str | None = None,
8986
) -> None:
9087
model = _pop_optional_str(kwargs, "transcription_model")
9188
SpeechService.__init__(
@@ -98,8 +95,8 @@ def initialize_speech_service(
9895
service.additional_kwargs.update(kwargs)
9996

10097

101-
def timestamps_to_word_boundaries(segments: t.Sequence[TranscriptionSegment]) -> t.List[WordBoundary]:
102-
word_boundaries: t.List[WordBoundary] = []
98+
def timestamps_to_word_boundaries(segments: t.Sequence[TranscriptionSegment]) -> list[WordBoundary]:
99+
word_boundaries: list[WordBoundary] = []
103100
current_text_offset = 0
104101
for segment in segments:
105102
for dict_ in segment["words"]:
@@ -128,9 +125,9 @@ class SpeechService(ABC):
128125
def __init__(
129126
self,
130127
global_speed: float = 1.00,
131-
cache_dir: t.Optional[PathLike] = None,
132-
transcription_model: t.Optional[str] = None,
133-
transcription_kwargs: t.Optional[t.Dict[str, object]] = None,
128+
cache_dir: PathLike | None = None,
129+
transcription_model: str | None = None,
130+
transcription_kwargs: dict[str, object] | None = None,
134131
**kwargs: object,
135132
) -> None:
136133
"""
@@ -155,8 +152,8 @@ def __init__(
155152
if not os.path.exists(self.cache_dir):
156153
os.makedirs(self.cache_dir)
157154

158-
self.transcription_model: t.Optional[str] = None
159-
self._whisper_model: t.Optional[WhisperModel] = None
155+
self.transcription_model: str | None = None
156+
self._whisper_model: WhisperModel | None = None
160157
self.set_transcription(
161158
model=transcription_model,
162159
kwargs={} if transcription_kwargs is None else transcription_kwargs,
@@ -172,7 +169,7 @@ def _wrap_generate_from_text(
172169
# Replace newlines with lines, reduce multiple consecutive spaces to single
173170
text = " ".join(text.split())
174171
raw_path = kwargs.pop("path", None)
175-
path: t.Optional[PathLike] = None
172+
path: PathLike | None = None
176173
if raw_path is not None:
177174
if not isinstance(raw_path, (str, os.PathLike)):
178175
raise TypeError("path must be a string or path-like object")
@@ -218,7 +215,7 @@ def _wrap_generate_from_text(
218215
append_voiceover_cache_entry(Path(self.cache_dir) / DEFAULT_VOICEOVER_CACHE_JSON_FILENAME, dict_)
219216
return dict_
220217

221-
def set_transcription(self, model: t.Optional[str] = None, kwargs: t.Optional[t.Dict[str, object]] = None) -> None:
218+
def set_transcription(self, model: str | None = None, kwargs: dict[str, object] | None = None) -> None:
222219
"""Set the transcription model and keyword arguments to be passed
223220
to the transcribe() function.
224221
@@ -264,8 +261,8 @@ def get_audio_basename(self, data: t.Mapping[str, JsonValue]) -> str:
264261
def generate_from_text(
265262
self,
266263
text: str,
267-
cache_dir: t.Optional[PathLike] = None,
268-
path: t.Optional[PathLike] = None,
264+
cache_dir: PathLike | None = None,
265+
path: PathLike | None = None,
269266
**kwargs: object,
270267
) -> VoiceoverData:
271268
"""Implement this method for each speech service. Refer to `AzureService` for an example.
@@ -284,7 +281,7 @@ def get_cached_result(
284281
self,
285282
input_data: t.Mapping[str, JsonValue],
286283
cache_dir: PathLike,
287-
) -> t.Optional[VoiceoverData]:
284+
) -> VoiceoverData | None:
288285
json_path = Path(cache_dir) / DEFAULT_VOICEOVER_CACHE_JSON_FILENAME
289286
requested_input_data = json_object(input_data)
290287
for entry in load_voiceover_cache(json_path):

0 commit comments

Comments
 (0)