-
Notifications
You must be signed in to change notification settings - Fork 1
/
tt_sounds.py
185 lines (157 loc) · 5.94 KB
/
tt_sounds.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
"""NoiseMaker class to play sounds for TagTracker.
Copyright (C) 2023-2024 Julias Hocking & Todd Glover
Notwithstanding the licensing information below, this code may not
be used in a commercial (for-profit, non-profit or government) setting
without the copyright-holder's written consent.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import subprocess
import random
# import tt_globals
import common.tt_constants as k
import common.tt_util as ut
import client_base_config as cfg
import tt_printer as pr
class NoiseMaker:
"""Make system sounds for TagTracker."""
# Class attributes
player = cfg.SOUND_PLAYER
enabled = cfg.SOUND_ENABLED
_initialized = False
# Queue for sound codes
_queue = []
# These can be files or folders full of sounds
bike_in = cfg.SOUND_BIKE_IN
bike_out = cfg.SOUND_BIKE_OUT
alert = cfg.SOUND_ALERT
cheer = cfg.SOUND_CHEER
@classmethod
def init_check(cls):
"""Check if the class is initialized, try to initialize if not.
If initialization fails prints an error message & returns False.
Also returns False
"""
if not cls.enabled:
return False
if cls._initialized:
return True
# Check that the player & sound files exist
player_missing = True
sounds_missing = True
if ut.find_on_path(cls.player):
player_missing = False
if all(
[
os.path.exists(cls.bike_in),
os.path.exists(cls.bike_out),
os.path.exists(cls.alert),
os.path.exists(cls.cheer)
]
):
sounds_missing = False
if player_missing or sounds_missing:
pr.iprint()
if sounds_missing:
pr.iprint(
"Some sound file(s) not found, some sounds may not play.",
style=k.WARNING_STYLE,
)
if player_missing:
pr.iprint(
"Missing sound-player, sounds are disabled.",
style=k.WARNING_STYLE,
)
cls.enabled = False
return False
cls._initialized = True
return True
@staticmethod
def _file_or_file_in_folder(filepath):
extension = "mp3"
# Check if the filepath exists
if not os.path.exists(filepath):
return None
# Check if the filepath is a file
if os.path.isfile(filepath):
return filepath
# If the filepath is a folder
if os.path.isdir(filepath):
# Get a list of files in the folder with the desired extension
matching_files = [f for f in os.listdir(filepath) if f.lower().endswith(extension)]
# If there are no matching files, return None
if not matching_files:
return None
# Select a random file from the list
random_file = random.choice(matching_files)
# Return the filepath of the random file
return os.path.join(filepath, random_file)
# Not a file nor a folder
return None
@classmethod
def get_sound_filepath(cls,code:str) -> str:
"""Fetch the soundfile for a given sound code."""
if code ==k.BIKE_IN:
look_at = cls.bike_in
elif code ==k.BIKE_OUT:
look_at = cls.bike_out
elif code ==k.ALERT:
look_at = cls.alert
elif code ==k.CHEER:
look_at = cls.cheer
else:
ut.squawk(f"sound type {code} not recognized")
return None
# Get the file or a file in folder if its a folder.
return cls._file_or_file_in_folder(look_at)
@classmethod
def play(cls, *sound_codes):
"""Play the sounds (which are constants from globals).
The sound_codes must be BIKE_IN, BIKE_OUT, or ALERT.
"""
if not cls.init_check() or not sound_codes:
return
soundfiles = []
for code in sound_codes:
if not code: # skip any non-codes
continue
soundfiles.append(cls.get_sound_filepath(code))
for sound in soundfiles:
if not sound: # skip any non-files
continue
if not os.path.exists(sound):
ut.squawk(f"sound file {sound} not found")
if not soundfiles:
return
# Try to play the sound, ignoring any or all errors
command = [cls.player] + soundfiles
subprocess.Popen(
command,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
stdin=subprocess.DEVNULL,
)
@classmethod
def queue_reset(cls):
"""Clears the sound queue."""
cls._queue = []
@classmethod
def queue_add(cls,*sound_codes):
"""Adds spund_code(s) to the sound queue."""
cls._queue.append(*sound_codes)
@classmethod
def queue_play(cls):
"""Play & reset the sounds queue."""
ut.squawk(f"{cls._queue=}",cfg.DEBUG)
if cls._queue:
cls.play(*cls._queue)
cls.queue_reset()