Edit on GitHub

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}
class Controller:
 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.

Controller(verbose: bool = False)
 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
def get_version(self, verbose: bool = False):
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
def verbose(self, status: bool = True):
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
def get_config(self):
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
def set_config( self, media_type=None, media_category=None, media_role=None, target=None, latency=None, rate=None, channels=None, channels_map=None, _format=None, volume=None, quality=None, verbose=False):
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

def load_list_targets(self, mode, verbose: bool = False):
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.

def get_list_targets(self, verbose: bool = False):
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"
    ]
}
}
def get_list_interfaces( self, filtered_by_type: str = True, type_interfaces: str = 'Client', verbose: bool = False):
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()
def playback(self, audio_filename: str = 'myplayback.wav', verbose: bool = False):
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
def record( self, audio_filename: str = 'myplayback.wav', timeout_seconds=5, verbose: bool = False):
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
def clear_devices(self, mode: str = 'all', verbose: bool = False):
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 under pw-cat, pw-play or pw-record.
Returns:
  • stdoutdict (dict) : a dictionary with keys of mode.
Example with pipewire:

pw-cat process