-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
created the amixer sget command parser - READY FOR REVIEW (#616)
* created the amixer first skeleton * push testing and integrate this commit and branch with issue: #591 * #591 checks the input data with jc utils * created the data parser of the sget control of the amixer sget <controller> command. * test commit - just for tests * another test commit * another test commit * created a dedicated pseudo algorithm for the amixer sget and tried various of strings. * orginized the docstring with general explanation about the tool and the amixer tool output and algorithm of the input parsing and input examples. * created raw implementation, but it's raw either or either. * orginized the content inside the amixer parser * removed endpoint name * added amixer to the jc parser in lib * more explanations * added tests for the amixer sget * added tests for the amixer sget * fine versioning fix * created docstring+another explanations seperated. * created the amixer parser docu * added the amixer in alphabet order to the json convert lib * Fix PEP 8: E302 violation as part of boy scout principle * deleted not necessary file * fixed the spaces between sections in the amixer description * resolved commits such as amixer module docstring and preperations for parser for raw=False. * Revert "Fix PEP 8: E302 violation as part of boy scout principle" This reverts commit 241d1a1. * created the dedicated _process for raw=False * created the dedicated _process for raw=False * added tests for the _process raw=False. * changed keys to be lowercase snake-case - Change 'dB' to 'db' * added more dB -> db changes and used int convertor of the jc utils --------- Co-authored-by: EdenRafael <[email protected]> Co-authored-by: Eden Refael <[email protected]> Co-authored-by: Kelly Brazil <[email protected]>
- Loading branch information
1 parent
f54ceaa
commit a39cb05
Showing
15 changed files
with
359 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
'acpi', | ||
'airport', | ||
'airport-s', | ||
'amixer', | ||
'apt-cache-show', | ||
'apt-get-sqq', | ||
'arp', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
r"""jc - JSON Convert `amixer sget` command output parser | ||
Usage (cli): | ||
$ amixer sget <control_name> | jc --amixer | ||
$ amixer sget Master | jc --amixer | ||
$ amixer sget Capture | jc --amixer | ||
$ amixer sget Speakers | jc --amixer | ||
Usage (module): | ||
import jc | ||
result = jc.parse('amixer', <amixer sget command output>) | ||
Schema: | ||
{ | ||
"control_name": string, | ||
"capabilities": [ | ||
string | ||
], | ||
"playback_channels": [ | ||
string | ||
], | ||
"limits": { | ||
"playback_min": integer, | ||
"playback_max": integer | ||
}, | ||
"mono": { | ||
"playback_value": integer, | ||
"percentage": integer, | ||
"db": float, | ||
"status": boolean | ||
} | ||
} | ||
Examples: | ||
$ amixer sget Master | jc --amixer -p | ||
{ | ||
"control_name": "Capture", | ||
"capabilities": [ | ||
"cvolume", | ||
"cswitch" | ||
], | ||
"playback_channels": [], | ||
"limits": { | ||
"playback_min": 0, | ||
"playback_max": 63 | ||
}, | ||
"front_left": { | ||
"playback_value": 63, | ||
"percentage": 100, | ||
"db": 30.0, | ||
"status": true | ||
}, | ||
"front_right": { | ||
"playback_value": 63, | ||
"percentage": 100, | ||
"db": 30.0, | ||
"status": true | ||
} | ||
} | ||
$ amixer sget Master | jc --amixer -p -r | ||
{ | ||
"control_name": "Master", | ||
"capabilities": [ | ||
"pvolume", | ||
"pvolume-joined", | ||
"pswitch", | ||
"pswitch-joined" | ||
], | ||
"playback_channels": [ | ||
"Mono" | ||
], | ||
"limits": { | ||
"playback_min": "0", | ||
"playback_max": "87" | ||
}, | ||
"mono": { | ||
"playback_value": "87", | ||
"percentage": "100%", | ||
"db": "0.00db", | ||
"status": "on" | ||
} | ||
} | ||
""" | ||
from typing import List, Dict | ||
|
||
import jc.utils | ||
from jc.utils import convert_to_int | ||
|
||
class info(): | ||
"""Provides parser metadata (version, author, etc.)""" | ||
version = '1.0' | ||
description = '`amixer` command parser' | ||
author = 'Eden Refael' | ||
author_email = '[email protected]' | ||
compatible = ['linux'] | ||
magic_commands = ['amixer'] | ||
tags = ['command'] | ||
|
||
|
||
__version__ = info.version | ||
|
||
|
||
def _process(proc_data: dict) -> dict: | ||
""" | ||
Processes raw structured data to match the schema requirements. | ||
Parameters: | ||
proc_data: (dict) raw structured data from the parser | ||
Returns: | ||
(dict) processed structured data adhering to the schema | ||
""" | ||
# Initialize the processed dictionary | ||
processed = { | ||
"control_name": proc_data.get("control_name", ""), | ||
"capabilities": proc_data.get("capabilities", []), | ||
"playback_channels": proc_data.get("playback_channels", []), | ||
"limits": { | ||
"playback_min": convert_to_int(proc_data.get("limits", {}).get("playback_min", 0)), | ||
"playback_max": convert_to_int(proc_data.get("limits", {}).get("playback_max", 0)), | ||
}, | ||
} | ||
|
||
# Process Mono or channel-specific data | ||
channels = ["mono", "front_left", "front_right"] | ||
for channel in channels: | ||
if channel in proc_data: | ||
channel_data = proc_data[channel] | ||
processed[channel] = { | ||
"playback_value": convert_to_int(channel_data.get("playback_value", 0)), | ||
"percentage": convert_to_int(channel_data.get("percentage", "0%").strip("%")), | ||
"db": float(channel_data.get("db", "0.0db").strip("db")), | ||
"status": channel_data.get("status", "off") == "on", | ||
} | ||
|
||
return processed | ||
|
||
|
||
def parse( | ||
data: str, | ||
raw: bool = False, | ||
quiet: bool = False | ||
) -> List[Dict]: | ||
""" | ||
Main text parsing function, The amixer is alsa mixer tool and output, Will work with Linux OS only. | ||
Parameters: | ||
data: (string) text data to parse | ||
raw: (boolean) unprocessed output if True | ||
quiet: (boolean) suppress warning messages if True | ||
Returns: | ||
List of Dictionaries. Raw or processed structured data. | ||
push test | ||
""" | ||
""" | ||
The Algorithm for parsing the `amixer sget` command, Input Explained/Rules/Pseudo Algorithm: | ||
1. There will always be the first line which tells the user about the control name. | ||
2. There will always be the Capabilities which include many of capabilities - It will be listed and separated by `" "`. | ||
3. After that we'll need to distinct between the Channel - Could be many of channels - It will be listed and separated | ||
by `" "`. | ||
3a. Capture channels - List of channels | ||
3b. Playback channels - List of channels | ||
4. Limits - We'll always have the minimum limit and the maximum limit. | ||
Input Example: | ||
1."":~$ amixer sget Capture | ||
Simple mixer control 'Capture',0 | ||
Capabilities: cvolume cswitch | ||
Capture channels: Front Left - Front Right | ||
Limits: Capture 0 - 63 | ||
Front Left: Capture 63 [100%] [30.00db] [on] | ||
Front Right: Capture 63 [100%] [30.00db] [on] | ||
2."":~$ amixer sget Master | ||
Simple mixer control 'Master',0 | ||
Capabilities: pvolume pvolume-joined pswitch pswitch-joined | ||
Playback channels: Mono | ||
Limits: Playback 0 - 87 | ||
Mono: Playback 87 [100%] [0.00db] [on] | ||
3."":~$ amixer sget Speaker | ||
Simple mixer control 'Speaker',0 | ||
Capabilities: pvolume pswitch | ||
Playback channels: Front Left - Front Right | ||
Limits: Playback 0 - 87 | ||
Mono: | ||
Front Left: Playback 87 [100%] [0.00db] [on] | ||
Front Right: Playback 87 [100%] [0.00db] [on] | ||
4."":~$ amixer sget Headphone | ||
Simple mixer control 'Headphone',0 | ||
Capabilities: pvolume pswitch | ||
Playback channels: Front Left - Front Right | ||
Limits: Playback 0 - 87 | ||
Mono: | ||
Front Left: Playback 0 [0%] [-65.25db] [off] | ||
Front Right: Playback 0 [0%] [-65.25db] [off] | ||
""" | ||
# checks os compatibility and print a stderr massage if not compatible. quiet True could remove this check. | ||
jc.utils.compatibility(__name__, info.compatible, quiet) | ||
|
||
# check if string | ||
jc.utils.input_type_check(data) | ||
|
||
# starts the parsing from here | ||
mapping = {} | ||
# split lines and than work on each line | ||
lines = data.splitlines() | ||
first_line = lines[0].strip() | ||
|
||
# Extract the control name from the first line | ||
if first_line.startswith("Simple mixer control"): | ||
control_name = first_line.split("'")[1] | ||
else: | ||
raise ValueError("Invalid amixer output format: missing control name.") | ||
# map the control name | ||
mapping["control_name"] = control_name | ||
|
||
# Process subsequent lines for capabilities, channels, limits, and channel-specific mapping. | ||
# gets the lines from the next line - because we already took care the first line. | ||
for line in lines[1:]: | ||
# strip the line (maybe there are white spaces in the begin&end) | ||
line = line.strip() | ||
|
||
if line.startswith("Capabilities:"): | ||
mapping["capabilities"] = line.split(":")[1].strip().split() | ||
elif line.startswith("Playback channels:"): | ||
mapping["playback_channels"] = line.split(":")[1].strip().split(" - ") | ||
elif line.startswith("Limits:"): | ||
limits = line.split(":")[1].strip().split(" - ") | ||
mapping["limits"] = { | ||
"playback_min": limits[0].split()[1], | ||
"playback_max": limits[1] | ||
} | ||
elif line.startswith("Mono:") or line.startswith("Front Left:") or line.startswith("Front Right:"): | ||
# Identify the channel name and parse its information | ||
channel_name = line.split(":")[0].strip().lower().replace(" ", "_") | ||
channel_info = line.split(":")[1].strip() | ||
# Example: "Playback 255 [100%] [0.00db] [on]" | ||
channel_data = channel_info.split(" ") | ||
if channel_data[0] == "": | ||
continue | ||
playback_value = channel_data[1] | ||
percentage = channel_data[2].strip("[]") # Extract percentage e.g., "100%" | ||
db_value = channel_data[3].strip("[]") # Extract db value e.g., "0.00db" | ||
status = channel_data[4].strip("[]") # Extract status e.g., "on" or "off" | ||
|
||
# Store channel mapping in the dictionary | ||
mapping[channel_name] = { | ||
"playback_value": playback_value, | ||
"percentage": percentage, | ||
"db": db_value.lower(), | ||
"status": status | ||
} | ||
|
||
return mapping if raw else _process(mapping) |
1 change: 1 addition & 0 deletions
1
tests/fixtures/ubuntu-22.04/amixer-control-capture-processed.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name":"Capture","capabilities":["cvolume","cswitch"],"playback_channels":[],"limits":{"playback_min":0,"playback_max":63},"front_left":{"playback_value":63,"percentage":100,"db":30.0,"status":true},"front_right":{"playback_value":63,"percentage":100,"db":30.0,"status":true}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name": "Capture", "capabilities": ["cvolume", "cswitch"], "limits": {"playback_min": "0", "playback_max": "63"}, "front_left": {"playback_value": "63", "percentage": "100%", "db": "30.00db", "status": "on"}, "front_right": {"playback_value": "63", "percentage": "100%", "db": "30.00db", "status": "on"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Simple mixer control 'Capture',0 | ||
Capabilities: cvolume cswitch | ||
Capture channels: Front Left - Front Right | ||
Limits: Capture 0 - 63 | ||
Front Left: Capture 63 [100%] [30.00dB] [on] | ||
Front Right: Capture 63 [100%] [30.00dB] [on] |
1 change: 1 addition & 0 deletions
1
tests/fixtures/ubuntu-22.04/amixer-control-headphone-processed.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name":"Headphone","capabilities":["pvolume","pswitch"],"playback_channels":["Front Left","Front Right"],"limits":{"playback_min":0,"playback_max":87},"front_left":{"playback_value":0,"percentage":0,"db":-65.25,"status":false},"front_right":{"playback_value":0,"percentage":0,"db":-65.25,"status":false}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name": "Headphone", "capabilities": ["pvolume", "pswitch"], "playback_channels": ["Front Left", "Front Right"], "limits": {"playback_min": "0", "playback_max": "87"}, "front_left": {"playback_value": "0", "percentage": "0%", "db": "-65.25db", "status": "off"}, "front_right": {"playback_value": "0", "percentage": "0%", "db": "-65.25db", "status": "off"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Simple mixer control 'Headphone',0 | ||
Capabilities: pvolume pswitch | ||
Playback channels: Front Left - Front Right | ||
Limits: Playback 0 - 87 | ||
Mono: | ||
Front Left: Playback 0 [0%] [-65.25dB] [off] | ||
Front Right: Playback 0 [0%] [-65.25dB] [off] |
1 change: 1 addition & 0 deletions
1
tests/fixtures/ubuntu-22.04/amixer-control-master-processed.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name":"Master","capabilities":["pvolume","pvolume-joined","pswitch","pswitch-joined"],"playback_channels":["Mono"],"limits":{"playback_min":0,"playback_max":87},"mono":{"playback_value":87,"percentage":100,"db":0.0,"status":true}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name": "Master", "capabilities": ["pvolume", "pvolume-joined", "pswitch", "pswitch-joined"], "playback_channels": ["Mono"], "limits": {"playback_min": "0", "playback_max": "87"}, "mono": {"playback_value": "87", "percentage": "100%", "db": "0.00db", "status": "on"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Simple mixer control 'Master',0 | ||
Capabilities: pvolume pvolume-joined pswitch pswitch-joined | ||
Playback channels: Mono | ||
Limits: Playback 0 - 87 | ||
Mono: Playback 87 [100%] [0.00dB] [on] |
1 change: 1 addition & 0 deletions
1
tests/fixtures/ubuntu-22.04/amixer-control-speakers-processed.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name":"Speaker","capabilities":["pvolume","pswitch"],"playback_channels":["Front Left","Front Right"],"limits":{"playback_min":0,"playback_max":87},"front_left":{"playback_value":87,"percentage":100,"db":0.0,"status":true},"front_right":{"playback_value":87,"percentage":100,"db":0.0,"status":true}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"control_name": "Speaker", "capabilities": ["pvolume", "pswitch"], "playback_channels": ["Front Left", "Front Right"], "limits": {"playback_min": "0", "playback_max": "87"}, "front_left": {"playback_value": "87", "percentage": "100%", "db": "0.00db", "status": "on"}, "front_right": {"playback_value": "87", "percentage": "100%", "db": "0.00db", "status": "on"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Simple mixer control 'Speaker',0 | ||
Capabilities: pvolume pswitch | ||
Playback channels: Front Left - Front Right | ||
Limits: Playback 0 - 87 | ||
Mono: | ||
Front Left: Playback 87 [100%] [0.00dB] [on] | ||
Front Right: Playback 87 [100%] [0.00dB] [on] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import unittest | ||
import jc.parsers.amixer | ||
import os | ||
import json | ||
|
||
THIS_DIR = os.path.dirname(os.path.abspath(__file__)) | ||
|
||
|
||
class AmixerTests(unittest.TestCase): | ||
AMIXER_CMD = 'amixer' | ||
UBUNTU_22_04_TEST_FIXTURES_PATH = f'{THIS_DIR}/fixtures/ubuntu-22.04/' | ||
AMIXER_CONTROL_PATH = f'{UBUNTU_22_04_TEST_FIXTURES_PATH}amixer-control-' | ||
TEST_FILES_NAME = [ | ||
f"{AMIXER_CONTROL_PATH}capture", | ||
f'{AMIXER_CONTROL_PATH}headphone', | ||
f'{AMIXER_CONTROL_PATH}master', | ||
f'{AMIXER_CONTROL_PATH}speakers', | ||
] | ||
|
||
def setUp(self): | ||
self.test_files_out = [f'{file}.out' for file in self.TEST_FILES_NAME] | ||
self.test_files_json = [f'{file}.json' for file in self.TEST_FILES_NAME] | ||
self.test_files_processed_json = [f'{file}-processed.json' for file in self.TEST_FILES_NAME] | ||
|
||
def test_amixer_sget(self): | ||
for file_out, file_json, file_processed_json in zip(self.test_files_out, self.test_files_json, | ||
self.test_files_processed_json): | ||
with open(file_out, 'r') as f: | ||
amixer_sget_raw_output: str = f.read() | ||
with open(file_json, 'r') as f: | ||
expected_amixer_sget_json_output: str = f.read() | ||
expected_amixer_sget_json_map: dict = json.loads(expected_amixer_sget_json_output) | ||
with open(file_processed_json, 'r') as f: | ||
expected_amixer_sget_processed_json_output: str = f.read() | ||
expected_amixer_sget_processed_json_map: dict = json.loads(expected_amixer_sget_processed_json_output) | ||
|
||
# Tests for raw=True | ||
amixer_sget_json_map: dict = jc.parse(self.AMIXER_CMD, amixer_sget_raw_output, raw=True, | ||
quiet=True) | ||
self.assertEqual(amixer_sget_json_map, expected_amixer_sget_json_map) | ||
# Tests for raw=False process | ||
amixer_sget_json_processed_map: dict = jc.parse(self.AMIXER_CMD, amixer_sget_raw_output, raw=False, | ||
quiet=True) | ||
self.assertEqual(amixer_sget_json_processed_map, expected_amixer_sget_processed_json_map) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |