1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-07-03 00:46:53 +02:00
Files
jc/jc/parsers/amixer.py
Eden Refael a39cb05228 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 241d1a1c63.

* 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 <eden.refael@kazuar.com>
Co-authored-by: Eden Refael <edeenraf@hotmail.com>
Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-12-20 15:06:38 -08:00

278 lines
8.6 KiB
Python

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 = 'edenraf@hotmail.com'
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)