import subprocess, re from collections import namedtuple class PacmdSection: def __init__(self, name, items): self.name = name # string self.items = items # [PacmdItem] class PacmdItem: def __init__(self, index, children): self.index = index # int self.children = children # {key: PacmdNode} class PacmdNode: def __init__(self, value, children): self.value = value # None | string | [string] self.children = children # {key: PacmdNode} class Pacmd: def __init__(self, full_output): lines = full_output.decode("utf-8").split("\n") self.infos = [] # [string] self.sections = [] # [PacmdSection] currsect = None curritem = None currpath = [] def close_curritem(): nonlocal curritem, currsect, currpath if curritem: currsect.items.append(curritem) curritem = None currpath = [] def close_currsect(): nonlocal self, currsect if currsect: close_curritem() self.sections.append(currsect) def q_is_key_value(ln): return ln.find("=") != -1 or ln.find(": ") != -1 def q_get_key_value(ln): if ln.find("=") != -1: m = re.search(r"^\s*([^=]*?)\s*=\s*(.*)$", ln) else: m = re.search(r"^\s*([^:]*):\s*(.*)$", ln) return m.group(1), m.group(2) for line in lines: line = line.rstrip() if len(line) == 0: continue line = line.replace("\t", " ") m = re.match(r"^( *(\* )?)([^ ].*)", line) is_default = m.group(2) is not None # TODO use is_default indent = len(m.group(1)) line = m.group(3) # print(currpath) # print(indent, line) if indent == 0: if line[0].isdigit(): close_currsect() name = re.search(r" ([^ ]*)\(s\)", line).group(1) currsect = PacmdSection(name, []) else: self.infos.append(line) elif indent == 4: close_curritem() index = re.search(r"index: ([0-9]*)$", line).group(1) curritem = PacmdItem(int(index), {}) elif indent >= 8: assert curritem != None is_aligned = indent % 8 == 0 indent_depth = (indent + 7) // 8 - 1 if is_aligned and indent_depth < len(currpath): currpath = currpath[:indent_depth] parentval, thisval = None, curritem for k in currpath: parentval, thisval = thisval, thisval.children[k] if q_is_key_value(line): key, value = q_get_key_value(line) thisval.children[key] = PacmdNode(value, {}) currpath.append(key) elif re.search(r":$", line) is not None: key = line[:-1] thisval.children[key] = PacmdNode(None, {}) currpath.append(key) elif indent_depth >= len(currpath): if type(thisval.value) != list: thisval.value = [thisval.value] thisval.value.append(line) else: print("line = " + line) assert False else: print("indent = " + str(indent)) assert False close_currsect() def pacmd(*args): return Pacmd(subprocess.check_output(["pacmd"] + list(args))) def list_all(): return pacmd("list") def list_sinks(): return pacmd("list-sinks") def list_sink_inputs(): return pacmd("list-sink-inputs") def _make_indent(n): return " " * n def dump_section(sect): print("Section " + sect.name + ":") for item in sect.items: print(_make_indent(1) + "Item " + str(item.index) + ":") for key, node in item.children.items(): dump_node(node, key, 2); def dump_node(node, key, indent): print(_make_indent(indent) + key + ":" + ("" if node.value is None else " " + str(node.value))) for k2, n2 in node.children.items(): dump_node(n2, k2, indent + 1)