playsound3 3.2.2__tar.gz → 3.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {playsound3-3.2.2 → playsound3-3.2.4}/PKG-INFO +5 -1
- {playsound3-3.2.2 → playsound3-3.2.4}/playsound3/__init__.py +1 -1
- {playsound3-3.2.2 → playsound3-3.2.4}/playsound3/playsound3.py +10 -7
- {playsound3-3.2.2 → playsound3-3.2.4}/pyproject.toml +8 -1
- playsound3-3.2.4/tests/sounds/sample3s.flac +0 -0
- playsound3-3.2.4/tests/sounds/sample3s.mp3 +0 -0
- playsound3-3.2.4/tests/sounds//320/267/320/262/321/203/320/272 /351/237/263 /350/201/262/351/237/263.wav +0 -0
- playsound3-3.2.4/tests/test_functionality.py +143 -0
- playsound3-3.2.4/tests/test_raising_errors.py +29 -0
- playsound3-3.2.4/tests/test_special_characters.py +25 -0
- {playsound3-3.2.2 → playsound3-3.2.4}/.gitignore +0 -0
- {playsound3-3.2.2 → playsound3-3.2.4}/LICENSE +0 -0
- {playsound3-3.2.2 → playsound3-3.2.4}/README.md +0 -0
- {playsound3-3.2.2 → playsound3-3.2.4}/playsound3/backends.py +0 -0
- {playsound3-3.2.2 → playsound3-3.2.4}/playsound3/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: playsound3
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.4
|
|
4
4
|
Summary: Cross-platform library to play audio files
|
|
5
5
|
Project-URL: Home, https://github.com/sjmikler/playsound3
|
|
6
6
|
Project-URL: Issues, https://github.com/sjmikler/playsound3/issues
|
|
@@ -29,6 +29,10 @@ Classifier: Typing :: Typed
|
|
|
29
29
|
Requires-Python: >=3.7
|
|
30
30
|
Requires-Dist: pywin32; sys_platform == 'win32'
|
|
31
31
|
Requires-Dist: typing-extensions; python_version < '3.8'
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pyright; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
32
36
|
Description-Content-Type: text/markdown
|
|
33
37
|
|
|
34
38
|
> **Version 3.0.0**
|
|
@@ -141,7 +141,10 @@ class Ffplay(SoundBackend):
|
|
|
141
141
|
return False
|
|
142
142
|
|
|
143
143
|
def play(self, sound: str) -> subprocess.Popen[bytes]:
|
|
144
|
-
return subprocess.Popen(
|
|
144
|
+
return subprocess.Popen(
|
|
145
|
+
["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", sound],
|
|
146
|
+
stdout=subprocess.DEVNULL,
|
|
147
|
+
)
|
|
145
148
|
|
|
146
149
|
|
|
147
150
|
class Wmplayer(SoundBackend):
|
|
@@ -342,16 +345,16 @@ AVAILABLE_BACKENDS: list[str] = [name for name in _BACKEND_PREFERENCE if _BACKEN
|
|
|
342
345
|
DEFAULT_BACKEND: str | None = _auto_select_backend()
|
|
343
346
|
|
|
344
347
|
|
|
345
|
-
# This
|
|
348
|
+
# This function is defined here at the bottom because of:
|
|
346
349
|
# SyntaxError: annotated name 'DEFAULT_BACKEND' can't be global
|
|
347
350
|
def prefer_backends(*backends: str) -> str | None:
|
|
348
351
|
"""Add backends to the top of the preference list.
|
|
349
352
|
|
|
350
|
-
This function
|
|
351
|
-
Backend selected here
|
|
352
|
-
|
|
353
|
-
without breaking the cross-platform functionality.
|
|
354
|
-
After updating the preferences,
|
|
353
|
+
This function overrides the default backend preference.
|
|
354
|
+
Backend selected here will be used ONLY if available on the system.
|
|
355
|
+
This means this function can be used to update the preference for a
|
|
356
|
+
specific platform without breaking the cross-platform functionality.
|
|
357
|
+
After updating the preferences, the new default backend is returned.
|
|
355
358
|
|
|
356
359
|
Args:
|
|
357
360
|
backends: Names of the backends to prefer.
|
|
@@ -45,11 +45,18 @@ Home = "https://github.com/sjmikler/playsound3"
|
|
|
45
45
|
Issues = "https://github.com/sjmikler/playsound3/issues"
|
|
46
46
|
Documentation = "https://github.com/sjmikler/playsound3/blob/main/README.md#quick-start"
|
|
47
47
|
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
dev = [
|
|
50
|
+
"pyright",
|
|
51
|
+
"pytest",
|
|
52
|
+
"ruff",
|
|
53
|
+
]
|
|
54
|
+
|
|
48
55
|
[tool.hatch.version]
|
|
49
56
|
path = "playsound3/__init__.py"
|
|
50
57
|
|
|
51
58
|
[tool.hatch.build.targets.sdist]
|
|
52
|
-
include = ["playsound3", "README.md"]
|
|
59
|
+
include = ["playsound3", "README.md", "tests"]
|
|
53
60
|
|
|
54
61
|
[tool.hatch.build.targets.wheel]
|
|
55
62
|
packages = ["playsound3"]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from playsound3 import AVAILABLE_BACKENDS, playsound
|
|
5
|
+
from playsound3.playsound3 import _prepare_path
|
|
6
|
+
|
|
7
|
+
loc_mp3_3s = "tests/sounds/sample3s.mp3"
|
|
8
|
+
loc_flc_3s = "tests/sounds/sample3s.flac"
|
|
9
|
+
web_wav_3s = "https://samplelib.com/lib/preview/wav/sample-3s.wav"
|
|
10
|
+
|
|
11
|
+
# Download web files to the local cache
|
|
12
|
+
for url in [web_wav_3s]:
|
|
13
|
+
_prepare_path(url)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_supported_sounds(backend):
|
|
17
|
+
not_supporting_flac = ["alsa", "winmm"]
|
|
18
|
+
|
|
19
|
+
if backend in not_supporting_flac:
|
|
20
|
+
return [loc_mp3_3s, web_wav_3s]
|
|
21
|
+
else:
|
|
22
|
+
return [loc_mp3_3s, loc_flc_3s, web_wav_3s]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
CI = os.environ.get("CI", False)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_blocking_1():
|
|
29
|
+
for backend in AVAILABLE_BACKENDS:
|
|
30
|
+
for path in get_supported_sounds(backend):
|
|
31
|
+
t0 = time.perf_counter()
|
|
32
|
+
sound = playsound(path, block=True, backend=backend)
|
|
33
|
+
|
|
34
|
+
td = time.perf_counter() - t0
|
|
35
|
+
assert not sound.is_alive(), f"backend={backend}, path={path}"
|
|
36
|
+
assert td >= 3.0, f"backend={backend}, path={path}"
|
|
37
|
+
assert CI or td < 5.0, f"backend={backend}, path={path}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_waiting_1():
|
|
41
|
+
for backend in AVAILABLE_BACKENDS:
|
|
42
|
+
for path in get_supported_sounds(backend):
|
|
43
|
+
t0 = time.perf_counter()
|
|
44
|
+
sound = playsound(path, block=False, backend=backend)
|
|
45
|
+
assert sound.is_alive(), f"backend={backend}, path={path}"
|
|
46
|
+
|
|
47
|
+
sound.wait()
|
|
48
|
+
td = time.perf_counter() - t0
|
|
49
|
+
assert not sound.is_alive(), f"backend={backend}, path={path}"
|
|
50
|
+
assert td >= 3.0, f"backend={backend}, path={path}"
|
|
51
|
+
assert CI or td < 5.0, f"backend={backend}, path={path}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_waiting_2():
|
|
55
|
+
for backend in AVAILABLE_BACKENDS:
|
|
56
|
+
for path in get_supported_sounds(backend):
|
|
57
|
+
sound = playsound(path, block=False, backend=backend)
|
|
58
|
+
assert sound.is_alive(), f"backend={backend}, path={path}"
|
|
59
|
+
|
|
60
|
+
time.sleep(5)
|
|
61
|
+
assert not sound.is_alive(), f"backend={backend}, path={path}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_stopping_1():
|
|
65
|
+
for backend in AVAILABLE_BACKENDS:
|
|
66
|
+
for path in get_supported_sounds(backend):
|
|
67
|
+
t0 = time.perf_counter()
|
|
68
|
+
sound = playsound(path, block=False, backend=backend)
|
|
69
|
+
assert sound.is_alive(), f"backend={backend}, path={path}"
|
|
70
|
+
|
|
71
|
+
time.sleep(1)
|
|
72
|
+
sound.stop()
|
|
73
|
+
td = time.perf_counter() - t0
|
|
74
|
+
|
|
75
|
+
time.sleep(0.05)
|
|
76
|
+
assert not sound.is_alive(), f"backend={backend}, path={path}"
|
|
77
|
+
assert td >= 0.95 and td < 2.0, f"backend={backend}, path={path}"
|
|
78
|
+
|
|
79
|
+
# Stopping again should be a no-op
|
|
80
|
+
sound.stop()
|
|
81
|
+
assert not sound.is_alive(), f"backend={backend}, path={path}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_parallel_1():
|
|
85
|
+
for backend in AVAILABLE_BACKENDS:
|
|
86
|
+
for path in get_supported_sounds(backend):
|
|
87
|
+
t0 = time.perf_counter()
|
|
88
|
+
sounds = [playsound(path, block=False, backend=backend) for _ in range(3)]
|
|
89
|
+
time.sleep(0.05)
|
|
90
|
+
for sound in sounds:
|
|
91
|
+
assert sound.is_alive(), f"backend={backend}"
|
|
92
|
+
time.sleep(1)
|
|
93
|
+
|
|
94
|
+
sounds[1].stop()
|
|
95
|
+
time.sleep(0.05)
|
|
96
|
+
assert sounds[0].is_alive(), f"backend={backend}"
|
|
97
|
+
assert sounds[2].is_alive(), f"backend={backend}"
|
|
98
|
+
assert not sounds[1].is_alive(), f"backend={backend}"
|
|
99
|
+
time.sleep(1)
|
|
100
|
+
|
|
101
|
+
assert sounds[0].is_alive(), f"backend={backend}"
|
|
102
|
+
assert sounds[2].is_alive(), f"backend={backend}"
|
|
103
|
+
sounds[0].stop()
|
|
104
|
+
sounds[2].stop()
|
|
105
|
+
td = time.perf_counter() - t0
|
|
106
|
+
|
|
107
|
+
time.sleep(0.05)
|
|
108
|
+
for sound in sounds:
|
|
109
|
+
assert not sound.is_alive(), f"backend={backend}"
|
|
110
|
+
assert td >= 2.0 and td < 3.0, f"backend={backend}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_parallel_2():
|
|
114
|
+
N_PARALLEL = 10 # Careful - this might be loud!
|
|
115
|
+
|
|
116
|
+
for backend in AVAILABLE_BACKENDS:
|
|
117
|
+
for path in get_supported_sounds(backend):
|
|
118
|
+
sounds = [playsound(path, block=False, backend=backend) for _ in range(N_PARALLEL)]
|
|
119
|
+
|
|
120
|
+
time.sleep(1)
|
|
121
|
+
for sound in sounds:
|
|
122
|
+
assert sound.is_alive(), f"backend={backend}, path={path}"
|
|
123
|
+
for sound in sounds:
|
|
124
|
+
sound.stop()
|
|
125
|
+
|
|
126
|
+
time.sleep(0.05)
|
|
127
|
+
for sound in sounds:
|
|
128
|
+
assert not sound.is_alive(), f"backend={backend}, path={path}"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_parallel_3():
|
|
132
|
+
for backend in AVAILABLE_BACKENDS:
|
|
133
|
+
sounds = [playsound(path, block=False, backend=backend) for path in get_supported_sounds(backend)]
|
|
134
|
+
|
|
135
|
+
time.sleep(1)
|
|
136
|
+
for sound in sounds:
|
|
137
|
+
assert sound.is_alive(), f"backend={backend}"
|
|
138
|
+
for sound in sounds:
|
|
139
|
+
sound.stop()
|
|
140
|
+
|
|
141
|
+
time.sleep(0.05)
|
|
142
|
+
for sound in sounds:
|
|
143
|
+
assert not sound.is_alive(), f"backend={backend}"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import urllib.error
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from playsound3 import playsound
|
|
6
|
+
from playsound3.playsound3 import PlaysoundException
|
|
7
|
+
|
|
8
|
+
valid = "tests/sounds/sample3s.mp3"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_invalid_sound_file():
|
|
12
|
+
with pytest.raises(PlaysoundException):
|
|
13
|
+
playsound("invalid.mp3")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_non_existent_file():
|
|
17
|
+
with pytest.raises(PlaysoundException):
|
|
18
|
+
playsound("non_existent.mp3")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_invalid_backend():
|
|
22
|
+
with pytest.raises(PlaysoundException):
|
|
23
|
+
playsound(valid, backend="invalid_backend")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_playsound_from_url():
|
|
27
|
+
url = "https://wrong-url.com/wrong-audio.mp3"
|
|
28
|
+
with pytest.raises(urllib.error.URLError):
|
|
29
|
+
playsound(url)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from playsound3 import AVAILABLE_BACKENDS, playsound
|
|
4
|
+
|
|
5
|
+
wav = "tests/sounds/звук 音 聲音.wav"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_with_blocking():
|
|
9
|
+
for backend in AVAILABLE_BACKENDS:
|
|
10
|
+
print(f"Testing backend: {backend}")
|
|
11
|
+
|
|
12
|
+
sound = playsound(wav, block=True, backend=backend)
|
|
13
|
+
assert not sound.is_alive()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_non_blocking():
|
|
17
|
+
for backend in AVAILABLE_BACKENDS:
|
|
18
|
+
print(f"Testing backend: {backend}")
|
|
19
|
+
|
|
20
|
+
sound = playsound(wav, block=False, backend=backend)
|
|
21
|
+
time.sleep(0.05)
|
|
22
|
+
assert sound.is_alive()
|
|
23
|
+
|
|
24
|
+
sound.wait()
|
|
25
|
+
assert not sound.is_alive()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|