pipewire_python.controller
Documentation
In the next pages you'll see documentation of each python component
controller.py
.
1""" 2## Documentation 3 4In the next pages you'll see documentation of each python component 5`controller.py`. 6""" 7 8import warnings 9 10# Loading internal functions 11from ._utils import ( 12 _drop_keys_with_none_values, 13 _execute_shell_command, 14 _filter_by_type, 15 _generate_command_by_dict, 16 _generate_dict_interfaces, 17 _generate_dict_list_targets, 18 _get_dict_from_stdout, 19) 20 21# Loading constants Constants.py 22from ._constants import MESSAGES_ERROR, RECOMMENDED_FORMATS, RECOMMENDED_RATES 23 24# [DEPRECATED] [FLAKE8] TO_AVOID_F401 PEP8 25# [DEPRECATED] https://stackoverflow.com/a/31079085/10491422 26# NOW USED IN DOCUMENTATION 27# __all__ = [ 28# # Classes and fucntions to doc 29# 'Controller', 30# # [DEPRECATED] Unused files pylint 31# # "_print_std", 32# # "_get_dict_from_stdout", 33# # "_update_dict_by_dict", 34# # "_drop_keys_with_none_values", 35# # "_generate_command_by_dict", 36# # "_execute_shell_command", 37# ] 38 39 40class Controller: 41 """ 42 Class that controls pipewire command line interface 43 with shell commands, handling outputs, loading default 44 configs and more. 45 """ 46 47 _pipewire_cli = { # Help 48 "--help": "--help", # -h 49 "--version": "--version", 50 "--remote": None, # -r 51 } 52 53 _pipewire_modes = { # Modes 54 "--playback": None, # -p 55 "--record": None, # -r 56 "--midi": None, # -m 57 } 58 59 _pipewire_list_targets = { # "--list-targets": None, 60 "list_playback": None, 61 "list_record": None, 62 } 63 64 _pipewire_configs = { # Configs 65 "--media-type": None, # *default=Audio 66 "--media-category": None, # *default=Playback 67 "--media-role": None, # *default=Music 68 "--target": None, # *default=auto 69 "--latency": None, # *default=100ms (SOURCE FILE if not specified) 70 "--rate": None, # *default=48000 71 "--channels": None, # [1,2] *default=2 72 "--channel-map": None, # ["stereo", "surround-51", "FL,FR"...] *default="FL,FR" 73 "--format": None, # [u8|s8|s16|s32|f32|f64] *default=s16 74 "--volume": None, # [0.0,1.0] *default=1.000 75 "--quality": None, # -q # [0,15] *default=4 76 "--verbose": None, # -v 77 } 78 79 _kill_pipewire = { 80 "all": ["kill", "$(pidof pw-cat)"], 81 "playback": ["kill", "$(pidof pw-play)"], 82 "record": ["kill", "$(pidof pw-record)"], 83 } 84 85 def __init__( 86 self, 87 # Debug 88 verbose: bool = False, 89 ): 90 """This constructor load default configs from OS executing 91 the following pipewire command 92 93 ```bash 94 #!/bin/bash 95 # Get defaults from output of: 96 pw-cat -h 97 ``` 98 """ 99 # LOAD ALL DEFAULT PARAMETERS 100 101 mycommand = ["pw-cat", "-h"] 102 103 # get default parameters with help 104 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) # stderr 105 # convert stdout to dictionary 106 dict_default_values = _get_dict_from_stdout(stdout=str(stdout.decode()), verbose=verbose) 107 108 if verbose: 109 print(self._pipewire_configs) 110 111 # Save default system configs to our json 112 self._pipewire_configs.update( 113 ([(key, dict_default_values[key]) for key in dict_default_values]) 114 ) 115 116 if verbose: 117 print(self._pipewire_configs) 118 119 # Delete keys with None values 120 self._pipewire_configs = _drop_keys_with_none_values(self._pipewire_configs) 121 122 if verbose: 123 print(self._pipewire_configs) 124 125 # Load values of list targets 126 self.load_list_targets(mode="playback", verbose=verbose) 127 self.load_list_targets(mode="record", verbose=verbose) 128 129 def _help_cli( 130 self, 131 # Debug 132 verbose: bool = True, 133 ): 134 """Get pipewire command line help""" 135 136 mycommand = ["pipewire", self._pipewire_cli["--help"]] 137 138 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) # stderr 139 140 return stdout 141 142 def get_version( 143 self, 144 # Debug 145 verbose: bool = False, 146 ): 147 """Get version of pipewire installed on OS by executing the following 148 code: 149 150 ```bash 151 #!/bin/bash 152 pw-cli --version 153 ``` 154 155 Args: 156 verbose (bool) : True enable debug logs. *default=False 157 158 Returns: 159 - versions (list) : Versions of pipewire compiled 160 """ 161 162 mycommand = ["pw-cli", "--version"] 163 164 if verbose: 165 print(f"[mycommand]{mycommand}") 166 167 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 168 versions = stdout.decode().split("\n")[1:] 169 170 self._pipewire_cli["--version"] = versions 171 172 return versions 173 174 def verbose( 175 self, 176 status: bool = True, 177 ): 178 """Get full log of pipewire stream status with the command `pw-cat` 179 180 An example of pw-cli usage is the code below: 181 182 ```bash 183 #!/bin/bash 184 # For example 185 pw-cat --playback beers.wav --verbose 186 ``` 187 188 that will generate an output like this: 189 190 ```bash 191 opened file "beers.wav" format 00010002 channels:2 rate:44100 192 using default channel map: FL,FR 193 rate=44100 channels=2 fmt=s16 samplesize=2 stride=4 latency=4410 (0.100s) 194 connecting playback stream; target_id=4294967295 195 stream state changed unconnected -> connecting 196 stream param change: id=2 197 stream properties: 198 media.type = "Audio" 199 ... 200 now=0 rate=0/0 ticks=0 delay=0 queued=0 201 remote 0 is named "pipewire-0" 202 core done 203 stream state changed connecting -> paused 204 stream param change: id=2 205 ... 206 stream param change: id=15 207 stream param change: id=15 208 now=13465394419270 rate=1/48000 ticks=35840 delay=512 queued=0 209 now=13466525228363 rate=1/48000 ticks=90112 delay=512 queued=0 210 ... 211 stream drained 212 stream state changed streaming -> paused 213 stream param change: id=4 214 stream state changed paused -> unconnected 215 stream param change: id=4 216 ``` 217 """ 218 219 if status: 220 self._pipewire_configs["--verbose"] = " " 221 else: 222 pass 223 224 def get_config(self): 225 """Return config dictionary with default or setup variables, remember that 226 this object changes only on python-side. Is not updated on real time, 227 For real-time, please create and destroy the class. 228 229 Args: 230 Nothing 231 232 Returns: 233 - _pipewire_configs (`dict`) : dictionary with config values 234 235 """ 236 237 return self._pipewire_configs 238 239 def set_config( 240 self, 241 # configs 242 media_type=None, 243 media_category=None, 244 media_role=None, 245 target=None, 246 latency=None, 247 rate=None, 248 channels=None, 249 channels_map=None, 250 _format=None, 251 volume=None, 252 quality=None, 253 # Debug 254 verbose=False, 255 ): 256 """Method that get args as variables and set them 257 to the `json` parameter of the class `_pipewire_configs`, 258 then you can use in other method, such as `playback(...)` or 259 `record(...)`. This method verifies values to avoid wrong 260 settings. 261 262 Args: 263 media_type : Set media type 264 media_category : Set media category 265 media_role : Set media role 266 target : Set node target 267 latency : Set node latency *example=100ms 268 rate : Set sample rate [8000,11025,16000,22050,44100,48000,88200,96000,176400,192000,352800,384000] 269 channels : Numbers of channels [1,2] 270 channels_map : ["stereo", "surround-51", "FL,FR", ...] 271 _format : ["u8", "s8", "s16", "s32", "f32", "f64"] 272 volume : Stream volume [0.000, 1.000] 273 quality : Resampler quality [0, 15] 274 verbose (`bool`): True enable debug logs. *default=False 275 276 Returns: 277 - Nothing 278 279 More: 280 Check all links listed at the beginning of this page 281 """ # 1 - media_type 282 if media_type: 283 self._pipewire_configs["--media-type"] = str(media_type) 284 elif media_type is None: 285 pass 286 else: 287 raise ValueError( 288 f"{MESSAGES_ERROR['ValueError']}[media_type='{media_type}'] EMPTY VALUE" 289 ) 290 # 2 - media_category 291 if media_category: 292 self._pipewire_configs["--media-category"] = str(media_category) 293 elif media_category is None: 294 pass 295 else: 296 raise ValueError( 297 f"{MESSAGES_ERROR['ValueError']}[media_category='{media_category}'] EMPTY VALUE" 298 ) 299 # 3 - media_role 300 if media_role: 301 self._pipewire_configs["--media-role"] = str(media_role) 302 elif media_role is None: 303 pass 304 else: 305 raise ValueError( 306 f"{MESSAGES_ERROR['ValueError']}[media_role='{media_role}'] EMPTY VALUE" 307 ) 308 # 4 - target 309 if target: 310 self._pipewire_configs["--target"] = str(target) 311 elif target is None: 312 pass 313 else: 314 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[target='{target}'] EMPTY VALUE") 315 # 5 - latency 316 if latency: 317 if any(chr.isdigit() for chr in latency): # Contain numbers 318 self._pipewire_configs["--latency"] = str(latency) 319 else: 320 raise ValueError( 321 f"{MESSAGES_ERROR['ValueError']}[latency='{latency}'] NO NUMBER IN VARIABLE" 322 ) 323 elif latency is None: 324 pass 325 else: 326 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[latency='{latency}'] EMPTY VALUE") 327 # 6 - rate 328 if rate: 329 if rate in RECOMMENDED_RATES: 330 self._pipewire_configs["--rate"] = str(rate) 331 else: 332 raise ValueError( 333 f"{MESSAGES_ERROR['ValueError']}[rate='{rate}']\ 334 VALUE NOT IN RECOMMENDED LIST \n{RECOMMENDED_RATES}" 335 ) 336 elif rate is None: 337 pass 338 else: 339 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[rate='{rate}'] EMPTY VALUE") 340 # 7 - channels 341 if channels: 342 if channels in [1, 2]: # values 343 self._pipewire_configs["--channels"] = str(channels) 344 else: 345 raise ValueError( 346 f"{MESSAGES_ERROR['ValueError']}[channels='{channels}']\ 347 WRONG VALUE\n ONLY 1 or 2." 348 ) 349 elif channels is None: 350 pass 351 else: 352 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[channels='{channels}'] EMPTY VALUE") 353 # 8 - channels-map 354 if channels_map: 355 self._pipewire_configs["--channels-map"] = str(channels_map) 356 elif channels_map is None: 357 pass 358 else: 359 raise ValueError( 360 f"{MESSAGES_ERROR['ValueError']}[channels_map='{channels_map}'] EMPTY VALUE" 361 ) 362 # 9 - format 363 if _format: 364 if _format in RECOMMENDED_FORMATS: 365 self._pipewire_configs["--format"] = str(_format) 366 else: 367 raise ValueError( 368 f"{MESSAGES_ERROR['ValueError']}[_format='{_format}']\ 369 VALUE NOT IN RECOMMENDED LIST \n{RECOMMENDED_FORMATS}" 370 ) 371 elif _format is None: 372 pass 373 else: 374 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[_format='{_format}'] EMPTY VALUE") 375 # 10 - volume 376 if volume: 377 if 0.0 <= volume <= 1.0: 378 self._pipewire_configs["--volume"] = str(volume) 379 else: 380 raise ValueError( 381 f"{MESSAGES_ERROR['ValueError']}[volume='{volume}']\ 382 OUT OF RANGE \n [0.000, 1.000]" 383 ) 384 elif volume is None: 385 pass 386 else: 387 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[volume='{volume}'] EMPTY VALUE") 388 # 11 - quality 389 if quality: 390 if 0 <= quality <= 15: 391 self._pipewire_configs["--quality"] = str(quality) 392 else: 393 raise ValueError( 394 f"{MESSAGES_ERROR['ValueError']}[quality='{quality}'] OUT OF RANGE \n [0, 15]" 395 ) 396 elif quality is None: 397 pass 398 else: 399 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[volume='{volume}'] EMPTY VALUE") 400 401 # 12 - verbose cli 402 if verbose: # True 403 self._pipewire_configs["--verbose"] = " " 404 else: 405 pass 406 407 if verbose: 408 print(self._pipewire_configs) 409 410 def load_list_targets( 411 self, 412 mode, # playback or record 413 # Debug, 414 verbose: bool = False, 415 ): 416 """Returns a list of targets to playback or record. Then you can use 417 the output to select a device to playback or record. 418 """ 419 420 if mode == "playback": 421 mycommand = ["pw-cat", "--playback", "--list-targets"] 422 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 423 self._pipewire_list_targets["list_playback"] = _generate_dict_list_targets( 424 longstring=stdout.decode(), verbose=verbose 425 ) 426 elif mode == "record": 427 mycommand = ["pw-cat", "--record", "--list-targets"] 428 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 429 self._pipewire_list_targets["list_record"] = _generate_dict_list_targets( 430 longstring=stdout.decode(), verbose=verbose 431 ) 432 else: 433 raise AttributeError(MESSAGES_ERROR["ValueError"]) 434 435 if verbose: 436 print(f"[mycommand]{mycommand}") 437 438 def get_list_targets( 439 self, 440 # Debug, 441 verbose: bool = False, 442 ): 443 """Returns a list of targets to playback or record. Then you can use 444 the output to select a device to playback or record. 445 446 Returns: 447 - `_pipewire_list_targets` 448 449 Examples: 450 ```python 451 >>> Controller().get_list_targets() 452 { 453 "list_playback": { 454 "86": { 455 "description": "Starship/Matisse HD Audio Controller Pro", 456 "prior": "936" 457 }, 458 "_list_nodes": [ 459 "86" 460 ], 461 "_node_default": [ 462 "86" 463 ], 464 "_alsa_node": [ 465 "alsa_output.pci-0000_0a_00.4.pro-output-0" 466 ] 467 }, 468 "list_record": { 469 "86": { 470 "description": "Starship/Matisse HD Audio Controller Pro", 471 "prior": "936" 472 }, 473 "_list_nodes": [ 474 "86" 475 ], 476 "_node_default": [ 477 "86" 478 ], 479 "_alsa_node": [ 480 "alsa_output.pci-0000_0a_00.4.pro-output-0" 481 ] 482 } 483 } 484 ``` 485 """ 486 if verbose: 487 print(self._pipewire_list_targets) 488 return self._pipewire_list_targets 489 490 def get_list_interfaces( 491 self, 492 filtered_by_type: str = True, 493 type_interfaces: str = "Client", 494 # Debug 495 verbose: bool = False, 496 ): 497 """Returns a list of applications currently using pipewire on Client. 498 An example of pw-cli usage is the code below: 499 500 ```bash 501 #!/bin/bash 502 pw-cli ls Client 503 ``` 504 Args: 505 filtered_by_type : If False, returns all. If not, returns a fitered dict 506 type_interfaces : Set type of Interface ["Client","Link","Node","Factory","Module","Metadata","Endpoint","Session","Endpoint Stream","EndpointLink","Port"] 507 508 Returns: 509 - dict_interfaces_filtered: dictionary with list of interfaces matching conditions 510 511 Examples: 512 ```python 513 >>> Controller().get_list_interfaces() 514 515 ``` 516 """ 517 mycommand = ["pw-cli", "info", "all"] 518 519 # if verbose: 520 # print(f"[mycommand]{mycommand}") 521 522 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 523 dict_interfaces = _generate_dict_interfaces(longstring=stdout.decode(), verbose=verbose) 524 525 if filtered_by_type: 526 dict_interfaces_filtered = _filter_by_type( 527 dict_interfaces=dict_interfaces, type_interfaces=type_interfaces 528 ) 529 else: 530 dict_interfaces_filtered = dict_interfaces 531 532 return dict_interfaces_filtered 533 534 def playback( 535 self, 536 audio_filename: str = "myplayback.wav", 537 # Debug 538 verbose: bool = False, 539 ): 540 """Execute pipewire command to play an audio file with the following 541 command: 542 543 ```bash 544 #!/bin/bash 545 pw-cat --playback {audio_filename} + {configs} 546 # configs are a concatenated params 547 ``` 548 549 Args: 550 audio_filename (`str`): Path of the file to be played. *default='myplayback.wav' 551 verbose (`bool`): True enable debug logs. *default=False 552 553 Returns: 554 - stdout (`str`): Shell response to the command in stdout format 555 - stderr (`str`): Shell response response to the command in stderr format 556 """ 557 warnings.warn("The name of the function may change on future releases", DeprecationWarning) 558 559 mycommand = ["pw-cat", "--playback", audio_filename] + _generate_command_by_dict( 560 mydict=self._pipewire_configs, verbose=verbose 561 ) 562 563 if verbose: 564 print(f"[mycommand]{mycommand}") 565 566 stdout, stderr = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 567 return stdout, stderr 568 569 def record( 570 self, 571 audio_filename: str = "myplayback.wav", 572 timeout_seconds=5, 573 # Debug 574 verbose: bool = False, 575 ): 576 """Execute pipewire command to record an audio file, with a timeout of 5 577 seconds with the following code and exiting the shell when tiomeout is over. 578 579 ```bash 580 #!/bin/bash 581 pw-cat --record {audio_filename} 582 # timeout is managed by python3 (when signal CTRL+C is sended) 583 ``` 584 585 Args: 586 audio_filename (`str`): Path of the file to be played. *default='myplayback.wav' 587 verbose (`bool`): True enable debug logs. *default=False 588 589 Returns: 590 - stdout (`str`): Shell response to the command in stdout format 591 - stderr (`str`): Shell response response to the command in stderr format 592 """ 593 warnings.warn("The name of the function may change on future releases", DeprecationWarning) 594 595 mycommand = ["pw-cat", "--record", audio_filename] + _generate_command_by_dict( 596 mydict=self._pipewire_configs, verbose=verbose 597 ) 598 599 if verbose: 600 print(f"[mycommand]{mycommand}") 601 602 stdout, stderr = _execute_shell_command( 603 command=mycommand, timeout=timeout_seconds, verbose=verbose 604 ) 605 return stdout, stderr 606 607 def clear_devices( 608 self, 609 mode: str = "all", # ['all','playback','record'] 610 # Debug 611 verbose: bool = False, 612 ): 613 """Function to stop process running under pipewire executed by 614 python controller and with default process name of `pw-cat`, `pw-play` or `pw-record`. 615 616 Args: 617 mode (`str`) : string to kill process under `pw-cat`, `pw-play` or `pw-record`. 618 619 Returns: 620 - stdoutdict (`dict`) : a dictionary with keys of `mode`. 621 622 Example with pipewire: 623 pw-cat process 624 """ 625 626 mycommand = self._kill_pipewire[mode] 627 628 if verbose: 629 print(f"[mycommands]{mycommand}") 630 631 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) 632 633 return {mode: stdout}
41class Controller: 42 """ 43 Class that controls pipewire command line interface 44 with shell commands, handling outputs, loading default 45 configs and more. 46 """ 47 48 _pipewire_cli = { # Help 49 "--help": "--help", # -h 50 "--version": "--version", 51 "--remote": None, # -r 52 } 53 54 _pipewire_modes = { # Modes 55 "--playback": None, # -p 56 "--record": None, # -r 57 "--midi": None, # -m 58 } 59 60 _pipewire_list_targets = { # "--list-targets": None, 61 "list_playback": None, 62 "list_record": None, 63 } 64 65 _pipewire_configs = { # Configs 66 "--media-type": None, # *default=Audio 67 "--media-category": None, # *default=Playback 68 "--media-role": None, # *default=Music 69 "--target": None, # *default=auto 70 "--latency": None, # *default=100ms (SOURCE FILE if not specified) 71 "--rate": None, # *default=48000 72 "--channels": None, # [1,2] *default=2 73 "--channel-map": None, # ["stereo", "surround-51", "FL,FR"...] *default="FL,FR" 74 "--format": None, # [u8|s8|s16|s32|f32|f64] *default=s16 75 "--volume": None, # [0.0,1.0] *default=1.000 76 "--quality": None, # -q # [0,15] *default=4 77 "--verbose": None, # -v 78 } 79 80 _kill_pipewire = { 81 "all": ["kill", "$(pidof pw-cat)"], 82 "playback": ["kill", "$(pidof pw-play)"], 83 "record": ["kill", "$(pidof pw-record)"], 84 } 85 86 def __init__( 87 self, 88 # Debug 89 verbose: bool = False, 90 ): 91 """This constructor load default configs from OS executing 92 the following pipewire command 93 94 ```bash 95 #!/bin/bash 96 # Get defaults from output of: 97 pw-cat -h 98 ``` 99 """ 100 # LOAD ALL DEFAULT PARAMETERS 101 102 mycommand = ["pw-cat", "-h"] 103 104 # get default parameters with help 105 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) # stderr 106 # convert stdout to dictionary 107 dict_default_values = _get_dict_from_stdout(stdout=str(stdout.decode()), verbose=verbose) 108 109 if verbose: 110 print(self._pipewire_configs) 111 112 # Save default system configs to our json 113 self._pipewire_configs.update( 114 ([(key, dict_default_values[key]) for key in dict_default_values]) 115 ) 116 117 if verbose: 118 print(self._pipewire_configs) 119 120 # Delete keys with None values 121 self._pipewire_configs = _drop_keys_with_none_values(self._pipewire_configs) 122 123 if verbose: 124 print(self._pipewire_configs) 125 126 # Load values of list targets 127 self.load_list_targets(mode="playback", verbose=verbose) 128 self.load_list_targets(mode="record", verbose=verbose) 129 130 def _help_cli( 131 self, 132 # Debug 133 verbose: bool = True, 134 ): 135 """Get pipewire command line help""" 136 137 mycommand = ["pipewire", self._pipewire_cli["--help"]] 138 139 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) # stderr 140 141 return stdout 142 143 def get_version( 144 self, 145 # Debug 146 verbose: bool = False, 147 ): 148 """Get version of pipewire installed on OS by executing the following 149 code: 150 151 ```bash 152 #!/bin/bash 153 pw-cli --version 154 ``` 155 156 Args: 157 verbose (bool) : True enable debug logs. *default=False 158 159 Returns: 160 - versions (list) : Versions of pipewire compiled 161 """ 162 163 mycommand = ["pw-cli", "--version"] 164 165 if verbose: 166 print(f"[mycommand]{mycommand}") 167 168 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 169 versions = stdout.decode().split("\n")[1:] 170 171 self._pipewire_cli["--version"] = versions 172 173 return versions 174 175 def verbose( 176 self, 177 status: bool = True, 178 ): 179 """Get full log of pipewire stream status with the command `pw-cat` 180 181 An example of pw-cli usage is the code below: 182 183 ```bash 184 #!/bin/bash 185 # For example 186 pw-cat --playback beers.wav --verbose 187 ``` 188 189 that will generate an output like this: 190 191 ```bash 192 opened file "beers.wav" format 00010002 channels:2 rate:44100 193 using default channel map: FL,FR 194 rate=44100 channels=2 fmt=s16 samplesize=2 stride=4 latency=4410 (0.100s) 195 connecting playback stream; target_id=4294967295 196 stream state changed unconnected -> connecting 197 stream param change: id=2 198 stream properties: 199 media.type = "Audio" 200 ... 201 now=0 rate=0/0 ticks=0 delay=0 queued=0 202 remote 0 is named "pipewire-0" 203 core done 204 stream state changed connecting -> paused 205 stream param change: id=2 206 ... 207 stream param change: id=15 208 stream param change: id=15 209 now=13465394419270 rate=1/48000 ticks=35840 delay=512 queued=0 210 now=13466525228363 rate=1/48000 ticks=90112 delay=512 queued=0 211 ... 212 stream drained 213 stream state changed streaming -> paused 214 stream param change: id=4 215 stream state changed paused -> unconnected 216 stream param change: id=4 217 ``` 218 """ 219 220 if status: 221 self._pipewire_configs["--verbose"] = " " 222 else: 223 pass 224 225 def get_config(self): 226 """Return config dictionary with default or setup variables, remember that 227 this object changes only on python-side. Is not updated on real time, 228 For real-time, please create and destroy the class. 229 230 Args: 231 Nothing 232 233 Returns: 234 - _pipewire_configs (`dict`) : dictionary with config values 235 236 """ 237 238 return self._pipewire_configs 239 240 def set_config( 241 self, 242 # configs 243 media_type=None, 244 media_category=None, 245 media_role=None, 246 target=None, 247 latency=None, 248 rate=None, 249 channels=None, 250 channels_map=None, 251 _format=None, 252 volume=None, 253 quality=None, 254 # Debug 255 verbose=False, 256 ): 257 """Method that get args as variables and set them 258 to the `json` parameter of the class `_pipewire_configs`, 259 then you can use in other method, such as `playback(...)` or 260 `record(...)`. This method verifies values to avoid wrong 261 settings. 262 263 Args: 264 media_type : Set media type 265 media_category : Set media category 266 media_role : Set media role 267 target : Set node target 268 latency : Set node latency *example=100ms 269 rate : Set sample rate [8000,11025,16000,22050,44100,48000,88200,96000,176400,192000,352800,384000] 270 channels : Numbers of channels [1,2] 271 channels_map : ["stereo", "surround-51", "FL,FR", ...] 272 _format : ["u8", "s8", "s16", "s32", "f32", "f64"] 273 volume : Stream volume [0.000, 1.000] 274 quality : Resampler quality [0, 15] 275 verbose (`bool`): True enable debug logs. *default=False 276 277 Returns: 278 - Nothing 279 280 More: 281 Check all links listed at the beginning of this page 282 """ # 1 - media_type 283 if media_type: 284 self._pipewire_configs["--media-type"] = str(media_type) 285 elif media_type is None: 286 pass 287 else: 288 raise ValueError( 289 f"{MESSAGES_ERROR['ValueError']}[media_type='{media_type}'] EMPTY VALUE" 290 ) 291 # 2 - media_category 292 if media_category: 293 self._pipewire_configs["--media-category"] = str(media_category) 294 elif media_category is None: 295 pass 296 else: 297 raise ValueError( 298 f"{MESSAGES_ERROR['ValueError']}[media_category='{media_category}'] EMPTY VALUE" 299 ) 300 # 3 - media_role 301 if media_role: 302 self._pipewire_configs["--media-role"] = str(media_role) 303 elif media_role is None: 304 pass 305 else: 306 raise ValueError( 307 f"{MESSAGES_ERROR['ValueError']}[media_role='{media_role}'] EMPTY VALUE" 308 ) 309 # 4 - target 310 if target: 311 self._pipewire_configs["--target"] = str(target) 312 elif target is None: 313 pass 314 else: 315 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[target='{target}'] EMPTY VALUE") 316 # 5 - latency 317 if latency: 318 if any(chr.isdigit() for chr in latency): # Contain numbers 319 self._pipewire_configs["--latency"] = str(latency) 320 else: 321 raise ValueError( 322 f"{MESSAGES_ERROR['ValueError']}[latency='{latency}'] NO NUMBER IN VARIABLE" 323 ) 324 elif latency is None: 325 pass 326 else: 327 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[latency='{latency}'] EMPTY VALUE") 328 # 6 - rate 329 if rate: 330 if rate in RECOMMENDED_RATES: 331 self._pipewire_configs["--rate"] = str(rate) 332 else: 333 raise ValueError( 334 f"{MESSAGES_ERROR['ValueError']}[rate='{rate}']\ 335 VALUE NOT IN RECOMMENDED LIST \n{RECOMMENDED_RATES}" 336 ) 337 elif rate is None: 338 pass 339 else: 340 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[rate='{rate}'] EMPTY VALUE") 341 # 7 - channels 342 if channels: 343 if channels in [1, 2]: # values 344 self._pipewire_configs["--channels"] = str(channels) 345 else: 346 raise ValueError( 347 f"{MESSAGES_ERROR['ValueError']}[channels='{channels}']\ 348 WRONG VALUE\n ONLY 1 or 2." 349 ) 350 elif channels is None: 351 pass 352 else: 353 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[channels='{channels}'] EMPTY VALUE") 354 # 8 - channels-map 355 if channels_map: 356 self._pipewire_configs["--channels-map"] = str(channels_map) 357 elif channels_map is None: 358 pass 359 else: 360 raise ValueError( 361 f"{MESSAGES_ERROR['ValueError']}[channels_map='{channels_map}'] EMPTY VALUE" 362 ) 363 # 9 - format 364 if _format: 365 if _format in RECOMMENDED_FORMATS: 366 self._pipewire_configs["--format"] = str(_format) 367 else: 368 raise ValueError( 369 f"{MESSAGES_ERROR['ValueError']}[_format='{_format}']\ 370 VALUE NOT IN RECOMMENDED LIST \n{RECOMMENDED_FORMATS}" 371 ) 372 elif _format is None: 373 pass 374 else: 375 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[_format='{_format}'] EMPTY VALUE") 376 # 10 - volume 377 if volume: 378 if 0.0 <= volume <= 1.0: 379 self._pipewire_configs["--volume"] = str(volume) 380 else: 381 raise ValueError( 382 f"{MESSAGES_ERROR['ValueError']}[volume='{volume}']\ 383 OUT OF RANGE \n [0.000, 1.000]" 384 ) 385 elif volume is None: 386 pass 387 else: 388 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[volume='{volume}'] EMPTY VALUE") 389 # 11 - quality 390 if quality: 391 if 0 <= quality <= 15: 392 self._pipewire_configs["--quality"] = str(quality) 393 else: 394 raise ValueError( 395 f"{MESSAGES_ERROR['ValueError']}[quality='{quality}'] OUT OF RANGE \n [0, 15]" 396 ) 397 elif quality is None: 398 pass 399 else: 400 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[volume='{volume}'] EMPTY VALUE") 401 402 # 12 - verbose cli 403 if verbose: # True 404 self._pipewire_configs["--verbose"] = " " 405 else: 406 pass 407 408 if verbose: 409 print(self._pipewire_configs) 410 411 def load_list_targets( 412 self, 413 mode, # playback or record 414 # Debug, 415 verbose: bool = False, 416 ): 417 """Returns a list of targets to playback or record. Then you can use 418 the output to select a device to playback or record. 419 """ 420 421 if mode == "playback": 422 mycommand = ["pw-cat", "--playback", "--list-targets"] 423 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 424 self._pipewire_list_targets["list_playback"] = _generate_dict_list_targets( 425 longstring=stdout.decode(), verbose=verbose 426 ) 427 elif mode == "record": 428 mycommand = ["pw-cat", "--record", "--list-targets"] 429 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 430 self._pipewire_list_targets["list_record"] = _generate_dict_list_targets( 431 longstring=stdout.decode(), verbose=verbose 432 ) 433 else: 434 raise AttributeError(MESSAGES_ERROR["ValueError"]) 435 436 if verbose: 437 print(f"[mycommand]{mycommand}") 438 439 def get_list_targets( 440 self, 441 # Debug, 442 verbose: bool = False, 443 ): 444 """Returns a list of targets to playback or record. Then you can use 445 the output to select a device to playback or record. 446 447 Returns: 448 - `_pipewire_list_targets` 449 450 Examples: 451 ```python 452 >>> Controller().get_list_targets() 453 { 454 "list_playback": { 455 "86": { 456 "description": "Starship/Matisse HD Audio Controller Pro", 457 "prior": "936" 458 }, 459 "_list_nodes": [ 460 "86" 461 ], 462 "_node_default": [ 463 "86" 464 ], 465 "_alsa_node": [ 466 "alsa_output.pci-0000_0a_00.4.pro-output-0" 467 ] 468 }, 469 "list_record": { 470 "86": { 471 "description": "Starship/Matisse HD Audio Controller Pro", 472 "prior": "936" 473 }, 474 "_list_nodes": [ 475 "86" 476 ], 477 "_node_default": [ 478 "86" 479 ], 480 "_alsa_node": [ 481 "alsa_output.pci-0000_0a_00.4.pro-output-0" 482 ] 483 } 484 } 485 ``` 486 """ 487 if verbose: 488 print(self._pipewire_list_targets) 489 return self._pipewire_list_targets 490 491 def get_list_interfaces( 492 self, 493 filtered_by_type: str = True, 494 type_interfaces: str = "Client", 495 # Debug 496 verbose: bool = False, 497 ): 498 """Returns a list of applications currently using pipewire on Client. 499 An example of pw-cli usage is the code below: 500 501 ```bash 502 #!/bin/bash 503 pw-cli ls Client 504 ``` 505 Args: 506 filtered_by_type : If False, returns all. If not, returns a fitered dict 507 type_interfaces : Set type of Interface ["Client","Link","Node","Factory","Module","Metadata","Endpoint","Session","Endpoint Stream","EndpointLink","Port"] 508 509 Returns: 510 - dict_interfaces_filtered: dictionary with list of interfaces matching conditions 511 512 Examples: 513 ```python 514 >>> Controller().get_list_interfaces() 515 516 ``` 517 """ 518 mycommand = ["pw-cli", "info", "all"] 519 520 # if verbose: 521 # print(f"[mycommand]{mycommand}") 522 523 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 524 dict_interfaces = _generate_dict_interfaces(longstring=stdout.decode(), verbose=verbose) 525 526 if filtered_by_type: 527 dict_interfaces_filtered = _filter_by_type( 528 dict_interfaces=dict_interfaces, type_interfaces=type_interfaces 529 ) 530 else: 531 dict_interfaces_filtered = dict_interfaces 532 533 return dict_interfaces_filtered 534 535 def playback( 536 self, 537 audio_filename: str = "myplayback.wav", 538 # Debug 539 verbose: bool = False, 540 ): 541 """Execute pipewire command to play an audio file with the following 542 command: 543 544 ```bash 545 #!/bin/bash 546 pw-cat --playback {audio_filename} + {configs} 547 # configs are a concatenated params 548 ``` 549 550 Args: 551 audio_filename (`str`): Path of the file to be played. *default='myplayback.wav' 552 verbose (`bool`): True enable debug logs. *default=False 553 554 Returns: 555 - stdout (`str`): Shell response to the command in stdout format 556 - stderr (`str`): Shell response response to the command in stderr format 557 """ 558 warnings.warn("The name of the function may change on future releases", DeprecationWarning) 559 560 mycommand = ["pw-cat", "--playback", audio_filename] + _generate_command_by_dict( 561 mydict=self._pipewire_configs, verbose=verbose 562 ) 563 564 if verbose: 565 print(f"[mycommand]{mycommand}") 566 567 stdout, stderr = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 568 return stdout, stderr 569 570 def record( 571 self, 572 audio_filename: str = "myplayback.wav", 573 timeout_seconds=5, 574 # Debug 575 verbose: bool = False, 576 ): 577 """Execute pipewire command to record an audio file, with a timeout of 5 578 seconds with the following code and exiting the shell when tiomeout is over. 579 580 ```bash 581 #!/bin/bash 582 pw-cat --record {audio_filename} 583 # timeout is managed by python3 (when signal CTRL+C is sended) 584 ``` 585 586 Args: 587 audio_filename (`str`): Path of the file to be played. *default='myplayback.wav' 588 verbose (`bool`): True enable debug logs. *default=False 589 590 Returns: 591 - stdout (`str`): Shell response to the command in stdout format 592 - stderr (`str`): Shell response response to the command in stderr format 593 """ 594 warnings.warn("The name of the function may change on future releases", DeprecationWarning) 595 596 mycommand = ["pw-cat", "--record", audio_filename] + _generate_command_by_dict( 597 mydict=self._pipewire_configs, verbose=verbose 598 ) 599 600 if verbose: 601 print(f"[mycommand]{mycommand}") 602 603 stdout, stderr = _execute_shell_command( 604 command=mycommand, timeout=timeout_seconds, verbose=verbose 605 ) 606 return stdout, stderr 607 608 def clear_devices( 609 self, 610 mode: str = "all", # ['all','playback','record'] 611 # Debug 612 verbose: bool = False, 613 ): 614 """Function to stop process running under pipewire executed by 615 python controller and with default process name of `pw-cat`, `pw-play` or `pw-record`. 616 617 Args: 618 mode (`str`) : string to kill process under `pw-cat`, `pw-play` or `pw-record`. 619 620 Returns: 621 - stdoutdict (`dict`) : a dictionary with keys of `mode`. 622 623 Example with pipewire: 624 pw-cat process 625 """ 626 627 mycommand = self._kill_pipewire[mode] 628 629 if verbose: 630 print(f"[mycommands]{mycommand}") 631 632 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) 633 634 return {mode: stdout}
Class that controls pipewire command line interface with shell commands, handling outputs, loading default configs and more.
86 def __init__( 87 self, 88 # Debug 89 verbose: bool = False, 90 ): 91 """This constructor load default configs from OS executing 92 the following pipewire command 93 94 ```bash 95 #!/bin/bash 96 # Get defaults from output of: 97 pw-cat -h 98 ``` 99 """ 100 # LOAD ALL DEFAULT PARAMETERS 101 102 mycommand = ["pw-cat", "-h"] 103 104 # get default parameters with help 105 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) # stderr 106 # convert stdout to dictionary 107 dict_default_values = _get_dict_from_stdout(stdout=str(stdout.decode()), verbose=verbose) 108 109 if verbose: 110 print(self._pipewire_configs) 111 112 # Save default system configs to our json 113 self._pipewire_configs.update( 114 ([(key, dict_default_values[key]) for key in dict_default_values]) 115 ) 116 117 if verbose: 118 print(self._pipewire_configs) 119 120 # Delete keys with None values 121 self._pipewire_configs = _drop_keys_with_none_values(self._pipewire_configs) 122 123 if verbose: 124 print(self._pipewire_configs) 125 126 # Load values of list targets 127 self.load_list_targets(mode="playback", verbose=verbose) 128 self.load_list_targets(mode="record", verbose=verbose)
This constructor load default configs from OS executing the following pipewire command
#!/bin/bash
# Get defaults from output of:
pw-cat -h
143 def get_version( 144 self, 145 # Debug 146 verbose: bool = False, 147 ): 148 """Get version of pipewire installed on OS by executing the following 149 code: 150 151 ```bash 152 #!/bin/bash 153 pw-cli --version 154 ``` 155 156 Args: 157 verbose (bool) : True enable debug logs. *default=False 158 159 Returns: 160 - versions (list) : Versions of pipewire compiled 161 """ 162 163 mycommand = ["pw-cli", "--version"] 164 165 if verbose: 166 print(f"[mycommand]{mycommand}") 167 168 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 169 versions = stdout.decode().split("\n")[1:] 170 171 self._pipewire_cli["--version"] = versions 172 173 return versions
Get version of pipewire installed on OS by executing the following code:
#!/bin/bash
pw-cli --version
Arguments:
- verbose (bool) : True enable debug logs. *default=False
Returns:
- versions (list) : Versions of pipewire compiled
175 def verbose( 176 self, 177 status: bool = True, 178 ): 179 """Get full log of pipewire stream status with the command `pw-cat` 180 181 An example of pw-cli usage is the code below: 182 183 ```bash 184 #!/bin/bash 185 # For example 186 pw-cat --playback beers.wav --verbose 187 ``` 188 189 that will generate an output like this: 190 191 ```bash 192 opened file "beers.wav" format 00010002 channels:2 rate:44100 193 using default channel map: FL,FR 194 rate=44100 channels=2 fmt=s16 samplesize=2 stride=4 latency=4410 (0.100s) 195 connecting playback stream; target_id=4294967295 196 stream state changed unconnected -> connecting 197 stream param change: id=2 198 stream properties: 199 media.type = "Audio" 200 ... 201 now=0 rate=0/0 ticks=0 delay=0 queued=0 202 remote 0 is named "pipewire-0" 203 core done 204 stream state changed connecting -> paused 205 stream param change: id=2 206 ... 207 stream param change: id=15 208 stream param change: id=15 209 now=13465394419270 rate=1/48000 ticks=35840 delay=512 queued=0 210 now=13466525228363 rate=1/48000 ticks=90112 delay=512 queued=0 211 ... 212 stream drained 213 stream state changed streaming -> paused 214 stream param change: id=4 215 stream state changed paused -> unconnected 216 stream param change: id=4 217 ``` 218 """ 219 220 if status: 221 self._pipewire_configs["--verbose"] = " " 222 else: 223 pass
Get full log of pipewire stream status with the command pw-cat
An example of pw-cli usage is the code below:
#!/bin/bash
# For example
pw-cat --playback beers.wav --verbose
that will generate an output like this:
opened file "beers.wav" format 00010002 channels:2 rate:44100
using default channel map: FL,FR
rate=44100 channels=2 fmt=s16 samplesize=2 stride=4 latency=4410 (0.100s)
connecting playback stream; target_id=4294967295
stream state changed unconnected -> connecting
stream param change: id=2
stream properties:
media.type = "Audio"
...
now=0 rate=0/0 ticks=0 delay=0 queued=0
remote 0 is named "pipewire-0"
core done
stream state changed connecting -> paused
stream param change: id=2
...
stream param change: id=15
stream param change: id=15
now=13465394419270 rate=1/48000 ticks=35840 delay=512 queued=0
now=13466525228363 rate=1/48000 ticks=90112 delay=512 queued=0
...
stream drained
stream state changed streaming -> paused
stream param change: id=4
stream state changed paused -> unconnected
stream param change: id=4
225 def get_config(self): 226 """Return config dictionary with default or setup variables, remember that 227 this object changes only on python-side. Is not updated on real time, 228 For real-time, please create and destroy the class. 229 230 Args: 231 Nothing 232 233 Returns: 234 - _pipewire_configs (`dict`) : dictionary with config values 235 236 """ 237 238 return self._pipewire_configs
Return config dictionary with default or setup variables, remember that this object changes only on python-side. Is not updated on real time, For real-time, please create and destroy the class.
Arguments:
- Nothing
Returns:
- _pipewire_configs (
dict
) : dictionary with config values
240 def set_config( 241 self, 242 # configs 243 media_type=None, 244 media_category=None, 245 media_role=None, 246 target=None, 247 latency=None, 248 rate=None, 249 channels=None, 250 channels_map=None, 251 _format=None, 252 volume=None, 253 quality=None, 254 # Debug 255 verbose=False, 256 ): 257 """Method that get args as variables and set them 258 to the `json` parameter of the class `_pipewire_configs`, 259 then you can use in other method, such as `playback(...)` or 260 `record(...)`. This method verifies values to avoid wrong 261 settings. 262 263 Args: 264 media_type : Set media type 265 media_category : Set media category 266 media_role : Set media role 267 target : Set node target 268 latency : Set node latency *example=100ms 269 rate : Set sample rate [8000,11025,16000,22050,44100,48000,88200,96000,176400,192000,352800,384000] 270 channels : Numbers of channels [1,2] 271 channels_map : ["stereo", "surround-51", "FL,FR", ...] 272 _format : ["u8", "s8", "s16", "s32", "f32", "f64"] 273 volume : Stream volume [0.000, 1.000] 274 quality : Resampler quality [0, 15] 275 verbose (`bool`): True enable debug logs. *default=False 276 277 Returns: 278 - Nothing 279 280 More: 281 Check all links listed at the beginning of this page 282 """ # 1 - media_type 283 if media_type: 284 self._pipewire_configs["--media-type"] = str(media_type) 285 elif media_type is None: 286 pass 287 else: 288 raise ValueError( 289 f"{MESSAGES_ERROR['ValueError']}[media_type='{media_type}'] EMPTY VALUE" 290 ) 291 # 2 - media_category 292 if media_category: 293 self._pipewire_configs["--media-category"] = str(media_category) 294 elif media_category is None: 295 pass 296 else: 297 raise ValueError( 298 f"{MESSAGES_ERROR['ValueError']}[media_category='{media_category}'] EMPTY VALUE" 299 ) 300 # 3 - media_role 301 if media_role: 302 self._pipewire_configs["--media-role"] = str(media_role) 303 elif media_role is None: 304 pass 305 else: 306 raise ValueError( 307 f"{MESSAGES_ERROR['ValueError']}[media_role='{media_role}'] EMPTY VALUE" 308 ) 309 # 4 - target 310 if target: 311 self._pipewire_configs["--target"] = str(target) 312 elif target is None: 313 pass 314 else: 315 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[target='{target}'] EMPTY VALUE") 316 # 5 - latency 317 if latency: 318 if any(chr.isdigit() for chr in latency): # Contain numbers 319 self._pipewire_configs["--latency"] = str(latency) 320 else: 321 raise ValueError( 322 f"{MESSAGES_ERROR['ValueError']}[latency='{latency}'] NO NUMBER IN VARIABLE" 323 ) 324 elif latency is None: 325 pass 326 else: 327 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[latency='{latency}'] EMPTY VALUE") 328 # 6 - rate 329 if rate: 330 if rate in RECOMMENDED_RATES: 331 self._pipewire_configs["--rate"] = str(rate) 332 else: 333 raise ValueError( 334 f"{MESSAGES_ERROR['ValueError']}[rate='{rate}']\ 335 VALUE NOT IN RECOMMENDED LIST \n{RECOMMENDED_RATES}" 336 ) 337 elif rate is None: 338 pass 339 else: 340 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[rate='{rate}'] EMPTY VALUE") 341 # 7 - channels 342 if channels: 343 if channels in [1, 2]: # values 344 self._pipewire_configs["--channels"] = str(channels) 345 else: 346 raise ValueError( 347 f"{MESSAGES_ERROR['ValueError']}[channels='{channels}']\ 348 WRONG VALUE\n ONLY 1 or 2." 349 ) 350 elif channels is None: 351 pass 352 else: 353 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[channels='{channels}'] EMPTY VALUE") 354 # 8 - channels-map 355 if channels_map: 356 self._pipewire_configs["--channels-map"] = str(channels_map) 357 elif channels_map is None: 358 pass 359 else: 360 raise ValueError( 361 f"{MESSAGES_ERROR['ValueError']}[channels_map='{channels_map}'] EMPTY VALUE" 362 ) 363 # 9 - format 364 if _format: 365 if _format in RECOMMENDED_FORMATS: 366 self._pipewire_configs["--format"] = str(_format) 367 else: 368 raise ValueError( 369 f"{MESSAGES_ERROR['ValueError']}[_format='{_format}']\ 370 VALUE NOT IN RECOMMENDED LIST \n{RECOMMENDED_FORMATS}" 371 ) 372 elif _format is None: 373 pass 374 else: 375 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[_format='{_format}'] EMPTY VALUE") 376 # 10 - volume 377 if volume: 378 if 0.0 <= volume <= 1.0: 379 self._pipewire_configs["--volume"] = str(volume) 380 else: 381 raise ValueError( 382 f"{MESSAGES_ERROR['ValueError']}[volume='{volume}']\ 383 OUT OF RANGE \n [0.000, 1.000]" 384 ) 385 elif volume is None: 386 pass 387 else: 388 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[volume='{volume}'] EMPTY VALUE") 389 # 11 - quality 390 if quality: 391 if 0 <= quality <= 15: 392 self._pipewire_configs["--quality"] = str(quality) 393 else: 394 raise ValueError( 395 f"{MESSAGES_ERROR['ValueError']}[quality='{quality}'] OUT OF RANGE \n [0, 15]" 396 ) 397 elif quality is None: 398 pass 399 else: 400 raise ValueError(f"{MESSAGES_ERROR['ValueError']}[volume='{volume}'] EMPTY VALUE") 401 402 # 12 - verbose cli 403 if verbose: # True 404 self._pipewire_configs["--verbose"] = " " 405 else: 406 pass 407 408 if verbose: 409 print(self._pipewire_configs)
Method that get args as variables and set them
to the json
parameter of the class _pipewire_configs
,
then you can use in other method, such as playback(...)
or
record(...)
. This method verifies values to avoid wrong
settings.
Arguments:
- media_type : Set media type
- media_category : Set media category
- media_role : Set media role
- target : Set node target
- latency : Set node latency *example=100ms
- rate : Set sample rate [8000,11025,16000,22050,44100,48000,88200,96000,176400,192000,352800,384000]
- channels : Numbers of channels [1,2]
- channels_map : ["stereo", "surround-51", "FL,FR", ...]
- _format : ["u8", "s8", "s16", "s32", "f32", "f64"]
- volume : Stream volume [0.000, 1.000]
- quality : Resampler quality [0, 15]
- verbose (
bool
): True enable debug logs. *default=False
Returns:
- Nothing
More:
Check all links listed at the beginning of this page
411 def load_list_targets( 412 self, 413 mode, # playback or record 414 # Debug, 415 verbose: bool = False, 416 ): 417 """Returns a list of targets to playback or record. Then you can use 418 the output to select a device to playback or record. 419 """ 420 421 if mode == "playback": 422 mycommand = ["pw-cat", "--playback", "--list-targets"] 423 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 424 self._pipewire_list_targets["list_playback"] = _generate_dict_list_targets( 425 longstring=stdout.decode(), verbose=verbose 426 ) 427 elif mode == "record": 428 mycommand = ["pw-cat", "--record", "--list-targets"] 429 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 430 self._pipewire_list_targets["list_record"] = _generate_dict_list_targets( 431 longstring=stdout.decode(), verbose=verbose 432 ) 433 else: 434 raise AttributeError(MESSAGES_ERROR["ValueError"]) 435 436 if verbose: 437 print(f"[mycommand]{mycommand}")
Returns a list of targets to playback or record. Then you can use the output to select a device to playback or record.
439 def get_list_targets( 440 self, 441 # Debug, 442 verbose: bool = False, 443 ): 444 """Returns a list of targets to playback or record. Then you can use 445 the output to select a device to playback or record. 446 447 Returns: 448 - `_pipewire_list_targets` 449 450 Examples: 451 ```python 452 >>> Controller().get_list_targets() 453 { 454 "list_playback": { 455 "86": { 456 "description": "Starship/Matisse HD Audio Controller Pro", 457 "prior": "936" 458 }, 459 "_list_nodes": [ 460 "86" 461 ], 462 "_node_default": [ 463 "86" 464 ], 465 "_alsa_node": [ 466 "alsa_output.pci-0000_0a_00.4.pro-output-0" 467 ] 468 }, 469 "list_record": { 470 "86": { 471 "description": "Starship/Matisse HD Audio Controller Pro", 472 "prior": "936" 473 }, 474 "_list_nodes": [ 475 "86" 476 ], 477 "_node_default": [ 478 "86" 479 ], 480 "_alsa_node": [ 481 "alsa_output.pci-0000_0a_00.4.pro-output-0" 482 ] 483 } 484 } 485 ``` 486 """ 487 if verbose: 488 print(self._pipewire_list_targets) 489 return self._pipewire_list_targets
Returns a list of targets to playback or record. Then you can use the output to select a device to playback or record.
Returns:
_pipewire_list_targets
Examples:
>>> Controller().get_list_targets()
{
"list_playback": {
"86": {
"description": "Starship/Matisse HD Audio Controller Pro",
"prior": "936"
},
"_list_nodes": [
"86"
],
"_node_default": [
"86"
],
"_alsa_node": [
"alsa_output.pci-0000_0a_00.4.pro-output-0"
]
},
"list_record": {
"86": {
"description": "Starship/Matisse HD Audio Controller Pro",
"prior": "936"
},
"_list_nodes": [
"86"
],
"_node_default": [
"86"
],
"_alsa_node": [
"alsa_output.pci-0000_0a_00.4.pro-output-0"
]
}
}
491 def get_list_interfaces( 492 self, 493 filtered_by_type: str = True, 494 type_interfaces: str = "Client", 495 # Debug 496 verbose: bool = False, 497 ): 498 """Returns a list of applications currently using pipewire on Client. 499 An example of pw-cli usage is the code below: 500 501 ```bash 502 #!/bin/bash 503 pw-cli ls Client 504 ``` 505 Args: 506 filtered_by_type : If False, returns all. If not, returns a fitered dict 507 type_interfaces : Set type of Interface ["Client","Link","Node","Factory","Module","Metadata","Endpoint","Session","Endpoint Stream","EndpointLink","Port"] 508 509 Returns: 510 - dict_interfaces_filtered: dictionary with list of interfaces matching conditions 511 512 Examples: 513 ```python 514 >>> Controller().get_list_interfaces() 515 516 ``` 517 """ 518 mycommand = ["pw-cli", "info", "all"] 519 520 # if verbose: 521 # print(f"[mycommand]{mycommand}") 522 523 stdout, _ = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 524 dict_interfaces = _generate_dict_interfaces(longstring=stdout.decode(), verbose=verbose) 525 526 if filtered_by_type: 527 dict_interfaces_filtered = _filter_by_type( 528 dict_interfaces=dict_interfaces, type_interfaces=type_interfaces 529 ) 530 else: 531 dict_interfaces_filtered = dict_interfaces 532 533 return dict_interfaces_filtered
Returns a list of applications currently using pipewire on Client. An example of pw-cli usage is the code below:
#!/bin/bash
pw-cli ls Client
Arguments:
- filtered_by_type : If False, returns all. If not, returns a fitered dict
- type_interfaces : Set type of Interface ["Client","Link","Node","Factory","Module","Metadata","Endpoint","Session","Endpoint Stream","EndpointLink","Port"]
Returns:
- dict_interfaces_filtered: dictionary with list of interfaces matching conditions
Examples:
>>> Controller().get_list_interfaces()
535 def playback( 536 self, 537 audio_filename: str = "myplayback.wav", 538 # Debug 539 verbose: bool = False, 540 ): 541 """Execute pipewire command to play an audio file with the following 542 command: 543 544 ```bash 545 #!/bin/bash 546 pw-cat --playback {audio_filename} + {configs} 547 # configs are a concatenated params 548 ``` 549 550 Args: 551 audio_filename (`str`): Path of the file to be played. *default='myplayback.wav' 552 verbose (`bool`): True enable debug logs. *default=False 553 554 Returns: 555 - stdout (`str`): Shell response to the command in stdout format 556 - stderr (`str`): Shell response response to the command in stderr format 557 """ 558 warnings.warn("The name of the function may change on future releases", DeprecationWarning) 559 560 mycommand = ["pw-cat", "--playback", audio_filename] + _generate_command_by_dict( 561 mydict=self._pipewire_configs, verbose=verbose 562 ) 563 564 if verbose: 565 print(f"[mycommand]{mycommand}") 566 567 stdout, stderr = _execute_shell_command(command=mycommand, timeout=-1, verbose=verbose) 568 return stdout, stderr
Execute pipewire command to play an audio file with the following command:
#!/bin/bash
pw-cat --playback {audio_filename} + {configs}
# configs are a concatenated params
Arguments:
- audio_filename (
str
): Path of the file to be played. *default='myplayback.wav' - verbose (
bool
): True enable debug logs. *default=False
Returns:
- stdout (
str
): Shell response to the command in stdout format- stderr (
str
): Shell response response to the command in stderr format
570 def record( 571 self, 572 audio_filename: str = "myplayback.wav", 573 timeout_seconds=5, 574 # Debug 575 verbose: bool = False, 576 ): 577 """Execute pipewire command to record an audio file, with a timeout of 5 578 seconds with the following code and exiting the shell when tiomeout is over. 579 580 ```bash 581 #!/bin/bash 582 pw-cat --record {audio_filename} 583 # timeout is managed by python3 (when signal CTRL+C is sended) 584 ``` 585 586 Args: 587 audio_filename (`str`): Path of the file to be played. *default='myplayback.wav' 588 verbose (`bool`): True enable debug logs. *default=False 589 590 Returns: 591 - stdout (`str`): Shell response to the command in stdout format 592 - stderr (`str`): Shell response response to the command in stderr format 593 """ 594 warnings.warn("The name of the function may change on future releases", DeprecationWarning) 595 596 mycommand = ["pw-cat", "--record", audio_filename] + _generate_command_by_dict( 597 mydict=self._pipewire_configs, verbose=verbose 598 ) 599 600 if verbose: 601 print(f"[mycommand]{mycommand}") 602 603 stdout, stderr = _execute_shell_command( 604 command=mycommand, timeout=timeout_seconds, verbose=verbose 605 ) 606 return stdout, stderr
Execute pipewire command to record an audio file, with a timeout of 5 seconds with the following code and exiting the shell when tiomeout is over.
#!/bin/bash
pw-cat --record {audio_filename}
# timeout is managed by python3 (when signal CTRL+C is sended)
Arguments:
- audio_filename (
str
): Path of the file to be played. *default='myplayback.wav' - verbose (
bool
): True enable debug logs. *default=False
Returns:
- stdout (
str
): Shell response to the command in stdout format- stderr (
str
): Shell response response to the command in stderr format
608 def clear_devices( 609 self, 610 mode: str = "all", # ['all','playback','record'] 611 # Debug 612 verbose: bool = False, 613 ): 614 """Function to stop process running under pipewire executed by 615 python controller and with default process name of `pw-cat`, `pw-play` or `pw-record`. 616 617 Args: 618 mode (`str`) : string to kill process under `pw-cat`, `pw-play` or `pw-record`. 619 620 Returns: 621 - stdoutdict (`dict`) : a dictionary with keys of `mode`. 622 623 Example with pipewire: 624 pw-cat process 625 """ 626 627 mycommand = self._kill_pipewire[mode] 628 629 if verbose: 630 print(f"[mycommands]{mycommand}") 631 632 stdout, _ = _execute_shell_command(command=mycommand, verbose=verbose) 633 634 return {mode: stdout}
Function to stop process running under pipewire executed by
python controller and with default process name of pw-cat
, pw-play
or pw-record
.
Arguments:
- mode (
str
) : string to kill process underpw-cat
,pw-play
orpw-record
.
Returns:
- stdoutdict (
dict
) : a dictionary with keys ofmode
.
Example with pipewire:
pw-cat process