I use the [MPD Music Player Daemon](https://www.musicpd.org/) as my main music player at home. I have a [Lenovo T420](/pc/lenovo/T420Laptop) in the corner (with broken screen) that acts as a general server. It has a Presonus audio interface attached, feeding into a mixer that then mixes sound with my TV and sends the result to a hifi amplifier. I run 16 user-mode instances of `mpd` so that I can have 16 simultaneous playlists and play states. These are on ports 6600..6615. I then have a helper script which generally defers to `mpc` (though also can wakeonlan and suspend the T420). This permits many shorthands for common tasks, such as ``` mp %pause # pause any playing instances mp 3,p # toggle play/pause on mpd instance 3 mp 4v7 # set volume to 70% on mpd instance 4 ``` In general, args are delimited by commas at the top level, so `mp` concatenates all arguments using spaces as a delimiter, and then splits by commas. A number on its own selects a particular `mpd` instance. If combined (as in `4v7`), then the `v7` command (which expands to `mpc volume 70`) is sent to instance 4, but the selected instance isn't changed. Thus ``` mp 3,4v7,v5,play ``` will expand to ``` mpc -h -p 6604 volume 70 mpc -h -p 6603 volume 50 mpc -h -p 6603 play ``` (You can see that it saves a lot of typing.) The scripts are below. # Source ## mp The help is out of date. A massive if/else construct is used since `match` didn't exist when I started writing this, and sometimes I want to do things like `if cmd[0] == "v"` and other logic. And I can't be bothered to rewrite it. ```py #!/usr/bin/python version = "2023-06-19" import os, sys, subprocess, time, re, math import platform from subprocess import run,PIPE import json try: from colors import color def col(x,c,**kw): if sys.stdout.isatty(): return color(x,fg=c,**kw) else: return x except: def col(x,c): return x mpdhost = os.getenv("MPD_HOST","t420b") try: # if mpdhpst is localhost, use 127.0.0.1 with open("/etc/hostname") as f: etchostname = f.read().strip() if etchostname.lower() == mpdhost.lower(): mpdhost = "127.0.0.1" except Exception: pass try: mpdport = int(os.getenv("MPD_PORT",6600)) except ValueError: print(f"MPD_PORT is not an integer, defaulting to 6600") modport = 6600 mpdbase = 6600 mpdports = [] def mpcmd(xs,**kw): cmd = ["mpc","-h",mpdhost,"-p",str(mpdport)]+xs #print(f"{cmd=}") return run(cmd,**kw) def validate_port(p): p = int(p) if p < 0 or p > 399: print(f"UI port offset must be in the range 1..399 -- got {p}") raise ValueError() return None p += 6600 return p def set_port(x): global mpdport p = validate_port(x) mpdport = p os.environ["MPD_PORT"] = str(p) def push_port(): mpdports.append(mpdport) def pop_port(): global mpdport p = mpdports.pop() mpdport = p os.environ["MPD_PORT"] = str(p) def getvol(): global last_play_state m = mpcmd(["status"],stdout=PIPE,stderr=PIPE) if m.returncode > 0: err = m.stderr.decode() print(f"{mpdhost}:{modport} -- error: {err.rstrip()}") return -1 o = m.stdout.decode() if "\n[playing]" in o: last_play_state = "playing" elif "\n[paused]" in o: last_play_state = "paused" else: last_play_state = "stopped" m = re.search(r"volume:\s*(\w+)",o) try: return int(m.group(1)) except ValueError: return -1 def gettrack(): m = mpcmd(["status"],stdout=PIPE,stderr=PIPE) if m.returncode > 0: err = m.stderr.decode() return col(f"--error: {err.rstrip()}","white",bg="red") x = m.stdout.decode().rstrip().splitlines() if len(x) == 1: return col("--stopped--","red") if len(x) == 2: if "Updating" in x[0]: return col("--stopped, updating db--","red") try: tr = x[0] st = x[1] for line in x: m = re.match(r"volume:\s*(\d+%|n/a)",line) if m: break else: return col("--failed to find volume--","red") except IndexError: print(f"# fail {len(x)} : {x=}") return col("--fail--","red") g = m.group(1) if "playing" in st: cx = "green" st = st.replace("[playing]",col("[playing]",cx)) st += " " + col("vol: "+g,cx) if "paused" in st: cx = "yellow" st = st.replace("[paused]",col("[paused]",cx)) st += " " + col("vol: "+g,cx) return f"{tr} -- {st}" def make_track_data( playstate="undefined", volume=0, repeat=False, random=False, single=False, consume=False, track_name="", pl_pos=0, pl_len=0, t_cur="", t_total="", t_pc=0): return { "playstate": playstate, "volume": volume, "repeat": repeat, "random": random, "single": single, "consume": consume, "track_name": track_name, "pl_pos": pl_pos, "pl_len": pl_len, "t_cur": t_cur, "t_total": t_total, "t_pc": t_pc } def parse_volume_line(x): m = re.match(r"volume: (\S+)\s+repeat: (\S+)\s+random: (\S+)\s+single: (\S+)\s+consume: (\S+)",x) if not m: return -1,False,False,False,False vol,rep,ran,sin,cons = m.groups() if vol == "n/a": vol = -1 else: vol = int(vol.rstrip("%")) rep = True if rep == "on" else False ran = True if ran == "on" else False sin = True if sin == "on" else False cons = True if cons == "on" else False return vol,rep,ran,sin,cons def parse_status_line(x): m = re.match(r"\[(\w+)\]\s+#(\d+)/(\d+)\s+([^/]+)/(\S+)\s+\((\d+)%\)",x) if not m: return -1,0,0,"#error4","#error4",0 else: state,pl_num,pl_len,t_cur,t_total,t_pc = m.groups() try: pl_num = int(pl_num) pl_len = int(pl_len) t_pc = float(t_pc) except ValueError: state = "error" return state,pl_num,pl_len,t_cur,t_total,t_pc def gettrack_data(): m = mpcmd(["status"],stdout=PIPE,stderr=PIPE) if m.returncode > 0: return make_track_data(playstate="error",track_name="#error1") x = m.stdout.decode().rstrip().splitlines() if len(x) == 1: vol,rep,ran,sin,cons = parse_volume_line(x[0]) track_name= "#stopped" return make_track_data(playstate="stopped",volume=vol,repeat=rep,random=ran,single=sin,consume=cons,track_name=track_name) if len(x) == 2: if "Updating" in x[0]: vol,rep,ran,sin,cons = parse_volume_line(x[1]) track_name= "#stopped,updating" return make_track_data(playstate="stopped",volume=vol,repeat=rep,random=ran,single=sin,consume=cons,track_name=track_name) x = [ y for y in x if not "Updating DB (" in y ] vol,rep,ran,sin,cons = parse_volume_line(x[2]) if vol < 0: track_name = "#error3" track_name = x[0] state,pl_pos,pl_len,t_cur,t_total,t_pc = parse_status_line(x[1]) return make_track_data(playstate=state,volume=vol,repeat=rep,random=ran,single=sin,consume=cons,track_name=track_name,pl_pos=pl_pos,pl_len=pl_len,t_cur=t_cur,t_total=t_total,t_pc=t_pc) def getplaylists(): m = mpcmd(["lsplaylists"],stdout=PIPE) x = m.stdout.decode().rstrip().splitlines() return x def setvol(newvol): newvol = max(min(newvol,100),0) # clamp mpcmd(["vol",str(newvol)],stdout=PIPE) def newline(): print() def get_width(): return os.get_terminal_size().columns def showline(x): width = get_width() padding = width - len(x) if padding < 2: print(f"\r{x}",end="") return padding -= 2 padding_r = padding // 2 padding_l = padding - padding_r pad_l = "="*padding_l pad_r = "="*padding_r print(f"\r{pad_l}[{x}]{pad_r}",end="") def help_both(): help_mp() print() help_mpc() def help_mpc(): print("""mpc commands (non mp commands passed through to mpc) consume - Consume mode (no arg means toggle) crossfade [] - Gets/set amount of crossfading between songs (0 disables) current [--wait] - Show the currently playing song (--wait waits until the song/state changes) queued - Show the currently queued (next) song next - Starts playing next song on queue. pause - Pauses playing. play - Starts playing the song-number specified (1 if no arg) prev - Starts playing previous song random - random mode (no arg means toggle) repeat - repeat mode (no arg means toggle) single - single mode if state (no arg means toggle) seek [+-][] or <[+-]<0-100>%> - Seeks by h:m:s or by %, +/- means relative, else absolute seekthrough [+-][] - seeks relative, possibly into other tracks stop - Stops playing toggle - Toggles between play and pause. at song number (use play) add - Adds a song from the music database to the queue insert - Like add except it adds song(s) after the currently playing one, rather than at the end clear - Empties the queue. crop - Remove all songs except for the currently playing song del - Removes a queue number from the queue (0 deletes current song) mv - alias of move move - Moves song at position to the position in the queue searchplay [ ]... - Search the queue for a matching song and play it. shuffle - Shuffles all songs on the queue. load - Loads as queue, playlists stored in ~/.mpd/playlists lsplaylists: - Lists available playlists playlist [] - Lists all songs in (current queue of no arg) rm - Deletes a specific playlist save - Saves playlist as listall [] - Lists from database (no arg means list all) ls [] - Lists all files/folders in directory (no arg means root) search [ ]... - Searches for substrings in song tags. Any number of tag type and query combinations can be specified. Possible tag types are: artist, album, title, track, name, genre, date, composer, performer, comment, disc, filename, or any (to match any tag). search - Searches with a filter expression, e.g. mpc search '((artist == "Kraftwerk") AND (title == "Metall auf Metall"))' Check the MPD protocol documentation for details. This syntax can be used with find and findadd as well. (Requires libmpdclient 2.16 and MPD 0.21) find [ ]... - Same as search, but tag values must match query exactly instead of doing a substring match. findadd [ ]... - Same as find, but add the result to the current queue instead of printing them. list [ ]... [group ]... - Return a list of all tags of given tag type. Optional search type/query limit results in a way similar to search. Results can be grouped by one or more tags. Example: e.g. mpc list album stats - Displays statistics about MPD. update [--wait] [] - Scans for updated files in the music directory. The optional parameter path (relative to the music directory) may limit the scope of the update. With --wait, mpc waits until MPD has finished the update. rescan [--wait] [] - Like update, but also rescans unmodified files. albumart - Download album art for the given song and write it to stdout. readpicture - Download a picture embedded in the given song and write it to stdout. Output Commands volume [+-] - Sets the volume to (0-100). If + or - is used, then it adjusts the volume relative to the current volume. outputs - Lists all available outputs Client-to-client Commands channels - List the channels that other clients have subscribed to. sendmessage - Send a message to the specified channel. waitmessage - Wait for at least one message on the specified channel. subscribe - Subscribe to the specified channel and continuously receive messages. idle [events] - Waits until an event occurs. Prints a list of event names, one per line. If you specify a list of events, only these events are considered. idleloop [events] - Similar to idle, but re-enters "idle" state after events have been printed. If you specify a list of events, only these events are considered. status [format] - Without an argument print a three line status output equivalent to "mpc" with no arguments. If a format string is given then the delimiters are processed exactly as how they are for metadata. See the '-f' option in Options Name Description %totaltime% The total duration of the song. %currenttime% The time that the client is currently at. %percenttime% The percentage of time elapsed for the current song. %songpos% The position of the current song within the playlist. %length% The number of songs within the playlist %state% Either 'playing' or 'paused' %volume% The current volume spaced out to 4 characters including a percent sign %random% Current status of random mode. 'on' or 'off' %repeat% Current status of repeat mode. 'on' or 'off' %single% Current status of single mode. 'on', 'once', or 'off' %consume% Current status of consume mode. 'on' or 'off' version - Reports the version of the protocol spoken, not the real version of the daemon. """) def help_mp(): print("""mp commands (other commands passed through to mpc) sleep - sleep : wait for n seconds wait - sleep : wait for n seconds (alias of sleep) getvol - get volume vol - set volume 43 to set to 43, +7 to increase by 7, -5 to decrease by 5, %43.5 to scale by 43.5% v - same as vol vXX - where XX is numeric, same as vol XX, but if X is in the range 1-9 it is multiplied by 10. sus - suspend (i.e. put machine to sleep) suspend - suspend (alias of sus) sys - suspend (alias of sus, common typo of sus) ping - ping music player wsh - wake and ssh to music player wash - wake and ssh (alias of wsh) sh - ssh to music player ssh - ssh to music player ftp - sftp to music player sftp - sftp to music player ft - sftp to music player f - sftp to music player z - random on/off (no arg means toggle) y - single on/off (no arg means toggle) s - single on/off (no arg means toggle) c - consume on/off (no arg means toggle) listp - list playlists lp - load playlist (by name or number, no arg means list playlists) pl - load playlist (alias of lp) r - repeat on/off (no arg means toggle) psus - pause and suspend wp - wake and play wui - wake and launch ui (ncmpcpp) ui - launch ui (ncmpcpp) gui - launch ui (alias of ui) wake - wake w - wake h - help on mp and mpc commands hmp - help on mp commands hmpc - help on mpc commands help - help on mp and mpc commands help_mp - help on mp commands help_mpc - help on mpc commands """) def exp_ports(x): portsel = set() if "-" in x: fr,to = x.split("-",1) try: fr = int(fr) to = int(to) if to < fr: fr,to = to,fr if fr < 0 or to < 0 or fr > 399 or to > 399: raise ValueError() except ValueError: print(f"Invalid port range: {x}") for y in range(fr,to+1): portsel.add(y) else: try: y = int(x) if y < 0 or y > 399: raise ValueError() portsel.add(int(x)) except ValueError: print(f"Invalid port {x}") return portsel def docmd(cmd,is_last=False): global mpdport, mpdhost, mpdbase if len(cmd) == 0: return c = cmd[0] if m := re.match(r"([\.\d]+)sl$",c): return docmd([f"sl{m.group(1)}"],is_last=is_last) elif m := re.match(r"sl([\.\d]+)$",c): a = m.group(1) try: t = float(a) except ValueError: print(f"Sleep time {a} not valid, must be a float or int") return return docmd(["sleep",t],is_last=is_last) elif c in ["sleep","wait"]: # sleep n -- wait for n seconds (can be float, default is 2) if len(cmd) > 1: t = float(cmd[1]) else: t = 2.0 t0 = int(math.floor(t)) t1 = t - t0 if t1 < 0.01: t1 = 0 print() showline(f"sleeping for {t} seconds") if t1 > 0: time.sleep(t1) while t0 > 0: showline(f"sleeping for {t0} seconds") t0 -= 1 time.sleep(1) showline(f"finished sleeping") newline() elif c[0] == "%": cmd=c[1:] if len(cmd) == 0: return if cmd[0] == "p": cmd = "pause" elif cmd[0] == "s": cmd = "stop" else: print(f"Not doing {cmd} en masse") return print(f"Doing {cmd} all") tr_range = os.getenv("TR_RANGE","0-15") portsel = set() try: portsel.update(exp_ports(tr_range)) except Exception: print(f"Invalid TR_RANGE: {tr_range}") push_port() for x in sorted(portsel): set_port(x) docmd([cmd]) pop_port() elif (c.isnumeric() and int(c) >= 0 and int(c) <= 399): # just a number sets the relative port if len(cmd) == 1: set_port(int(c)) else: push_port() set_port(int(c)) docmd(cmd[1:]) pop_port() elif c in ["port","rp","po"]: # port n, rp n, po n -- set or show relative port if len(cmd) == 1: print(f"port={mpdport}") else: set_port(cmd[1]) elif c in ["base"]: # set port base (you probably don't want to do this, but you can) if len(cmd) > 1: mpdbase = int(cmd[1]) else: mpdbase = int(os.getenv("MPD_PORT",6600)) elif c in ["start"]: # start mpd on host with selected port if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) for p in sorted(portsel): run(["ssh","-q",mpdhost,"mpdp",str(p)]) else: run(["ssh","-q",mpdhost,"mpdp",str(mpdport-mpdbase)]) elif c in ["help","h"]: # display mpc and mp help help_both() elif c in ["help_mpc","hmpc"]: # display mpc help help_mpc() elif c in ["host"]: # set host if len(cmd) > 1: mpdhost = cmd[1] else: mpdhost = os.getenv("MPD_HOST","localhost") elif c in ["help_mp","hmp"]: # display mp help (though not up to date) help_mp() elif c in ["getvol"]: # get volume print(getvol()) elif c in ["ver"]: # show version print(f"mp version: {version}") mpcmd(["ver"]) elif c in ["vol"]: # get/set volume if len(cmd) == 1: # with no args, shows volume as getvol does return docmd(["getvol"]) if cmd[1].startswith("+"): # +n adds n to volume vol = getvol() b = int(cmd[1][1:]) setvol(vol+b) elif cmd[1].startswith("-"): # -n subtracts n from volume vol = getvol() b = int(cmd[1][1:]) setvol(vol-b) elif cmd[1].startswith("%"): # n% sets volume to n percent of what it was before vol = getvol() b = int(cmd[1][1:])/100.0 setvol(int(b*vol)) else: #subprocess.run(["mpc","vol",cmd[1]]) # anything else is passed through to mpc mpcmd(["vol",cmd[1]]) elif c in ["vols"]: if len(cmd) == 1: docmd(["vols",os.getenv("TR_RANGE","0-15")]) else: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for x in sorted(portsel): set_port(x) v = getvol() if v == -1: t = col("--stopped--","red") else: if last_play_state == "playing": t = col(str(v)+"%","green") else: t = col(str(v)+"%","yellow") print(f"{mpdhost}:{x: >3} -- {t}") pop_port() elif c[0] == "v": # v n -- same as vol n # this "v" command case must be the last of all commands beginning with v if c == "v": cmd[0] = "vol" docmd(cmd) elif c[1:].isnumeric(): # e.g. v5 sets volume to 50%, v9 sets volume to 90% x = int(c[1:]) if x < 0: x = 0 elif x < 10: x *= 10 elif x > 100: x = 100 docmd(["vol",str(x)]) else: mpcmd(cmd) elif c in ["getp"]: # just get indices of playing mpds if len(cmd) == 1: return docmd(["getp","0-15"]) if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() pl = [] for x in sorted(portsel): set_port(x) y = gettrack() if "[playing]" in y: pl.append(x) print(" ".join(map(str,pl))) pop_port() elif c in ["isp"]: # get status of multiple running mpd's: st 3-5 gets status for mpds with rp=3,4,5 if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for x in sorted(portsel): set_port(x) m = subprocess.run(["mpc","status"],stdout=subprocess.PIPE) lines = m.stdout.decode().splitlines() for line in lines: if line[0] == "[": print(f"{mpdhost}:{x} -- {line[1:].split(']')[0]}") break else: print(f"{mpdhost}:{x} -- stopped") pop_port() else: docmd(["status"]) elif c in ["psd","paused"]: if len(cmd) == 1: return docmd(["paused","0-15"]) if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for x in sorted(portsel): set_port(x) y = gettrack() if "[paused]" in y: print(f"{mpdhost}:{x} -- {gettrack()}") pop_port() elif c in ["ply","playing"]: if len(cmd) == 1: return docmd(["playing","0-15"]) if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for x in sorted(portsel): set_port(x) y = gettrack() if "[playing]" in y: print(f"{mpdhost}:{x} -- {gettrack()}") pop_port() elif c.startswith("tr") and c[2:].isnumeric(): x = int(c[2:]) x = min(20,x) x = max(0,x) if x == 0: docmd(["tr","0"]) else: docmd(["tr",f"0-{x}"]) elif c in ["json"]: # json for mpd managers if len(cmd) == 1: # print(gettrack()) tr_range = os.getenv("TR_RANGE","0-15") docmd(["json",tr_range]) if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() track_data = [] for x in sorted(portsel): set_port(x) track_data.append(gettrack_data()) pop_port() json.dump(track_data,sys.stdout) exit() elif c in ["tr","track"]: # show playing track (can use multiple ports and ranges e.g. 1 3-4 if len(cmd) == 1: # print(gettrack()) tr_range = os.getenv("TR_RANGE","0-15") docmd(["tr",tr_range]) if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for x in sorted(portsel): set_port(x) print(f"{mpdhost}:{x: >3} -- {gettrack()}") pop_port() elif c in ["st"]: # get status of multiple running mpd's: st 3-5 gets status for mpds with rp=3,4,5 if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for x in sorted(portsel): print() print(f"Status for port: {x}") set_port(x) docmd(["status"]) pop_port() else: docmd(["status"]) elif c in ["sus","suspend","sys"]: # put the mpd computer to sleep (it needs 'sus' in its path) print(f"> suspending") subprocess.run(["ssh",mpdhost,"susp"]) elif c in ["ping"]: # ping the mpd computer p = platform.platform() npings = 16 if len(cmd) > 1: try: npings = int(cmd[1]) npings = min(32,max(0,npings)) except Exception: pass args = [] if "Linux" in p: pingargs = ["-c","1","-w","1"] for n in range(npings): m = subprocess.run(["ping"]+pingargs+[mpdhost]) if m.returncode == 0: break elif "macOS" in p: pingargs = ["-c","1","-W","1"] for n in range(npings): m = subprocess.run(["ping"]+pingargs+[mpdhost]) if m.returncode == 0: break else: # windows pingargs = ["-n","1","-w","1"] for n in range(npings): m = subprocess.run(["ping"]+pingargs+[mpdhost]) if m.returncode == 0: break elif c in ["wsh","wash"]: # wake and ssh into the mpd computer docmd(["wake"]) docmd(["ssh"]) elif c in ["sh","ssh"]: # ssh into the mpd computer subprocess.run(["ssh",mpdhost]) elif c in ["ftp","sftp","ft","f"]: # sftp into the mpd computer, optional arg is dir to cd to if len(cmd) > 1: d = cmd[1] host = f"{mpdhost}:{d}" else: host = mpdhost subprocess.run(["sftp",host]) elif c in ["z"]: # toggle random mode docmd(["random"]+cmd[1:]) elif c in ["y","s"]: # toggle single mode docmd(["single"]+cmd[1:]) elif c in ["c"]: # toggle consume mode docmd(["consume"]+cmd[1:]) elif c in ["listp"]: # list playlists in numbered list xs = getplaylists() nl = len(str(len(xs)+1)) for i,x in enumerate(xs): n = ((" "*nl)+str(i+1))[-nl:] print(f"{n}: {x}") elif c in ["lp","pl"]: # with no args is the same as listp, else loads playlist if len(cmd) > 1: pls = cmd[1:] xs = getplaylists() docmd(["clear"]) for x in pls: if x.isnumeric(): # can refer to playlist by its numerical index as given by listp i = int(x)-1 if i < len(xs): docmd(["load",xs[i]]) elif x in xs: # else try to load by name docmd(["load",x]) else: print(f"Playlist {x} does not exist") else: docmd(["listp"]) elif c in ["r"]: # toggle repeat mode docmd(["repeat"],cmd[1:]) elif c in ["psus"]: # pause and sus docmd(["pause"]) docmd(["sus"]) elif c in ["wp"]: # wake and play docmd(["w"]) docmd(["ping"]) docmd(["play"]) elif c in ["wui"]: # wake and load ncmpcpp docmd(["w"]) docmd(["ping"]) docmd(["ui"]+cmd[1:]) elif c in ["ui","gui"]: # load ncmpcpp if len(cmd) > 1: p = validate_port(cmd[1]) if p is not None: subprocess.run(['ncmpcpp','-p',str(p)]) else: subprocess.run(['ncmpcpp']) elif c in ["wake","w"]: # wake music pc using wmus print(f"> waking") subprocess.run(["wakey",mpdhost]) elif c == "p": # toggle play/pause if len(cmd) > 1: portsel = set() for x in cmd[1:]: portsel.update(exp_ports(x)) push_port() for p in sorted(portsel): set_port(p) docmd(["p"]) pop_port() else: print(f"> toggle play/pause (rport {mpdport-mpdbase})") docmd(["toggle"]) elif m := re.match(r"(\d+)(\w+)$",c): p,cx = m.groups() push_port() docmd([p]) docmd([cx]+cmd[1:]) pop_port() elif re.match(r"p(\d+)(-(\d+))?$",c): return docmd(["p",c[1:]]+cmd[1:]) else: # all else gets passed straight through to mpc print(f"> mpc {' '.join(cmd)}") mpcmd(cmd) #subprocess.run(["mpc"]+cmd) def main(): cmds = [[]] args = sys.argv[1:] if len(args) == 0: args = ["status"] argstr = " ".join(args) argsp = argstr.split(",") cmds = [] for arg in argsp: arg = arg.strip() xs = list(map(lambda t: t.strip(), re.split(r"\s+",arg))) cmds.append(xs) for arg in args: if ',' in arg: xs = arg.split(",") xs = list(map(lambda t: t.strip(),xs)) last_cmd_idx = len(cmds) - 1 for i,cmd in enumerate(cmds): docmd(cmd,i+1==last_cmd_idx) if __name__ == "__main__": try: main() except KeyboardInterrupt: print(f"Exiting due to keyboard interrupt.") exit(1) ``` ## make_new_mpd_conf This creates a subdirectory of `~/.mpd` and adjusts the port number and conf directory. ```py #!/usr/bin/env python import os import sys from subprocess import run from setproctitle import setproctitle args = sys.argv[1:] if len(args) == 0: args = ["1"] rp = int(args[0]) if rp < 0 or rp > 99: print(f"Invalid rp: {p}") exit(1) base_port = 6600 port = rp + base_port print(f"Using port {port}") home = os.getenv("HOME") mpd_dir = f"{home}/.mpd/mpd-{port}" mpd_conf=f"{mpd_dir}/mpd-{port}.conf" os.makedirs(mpd_dir,exist_ok=True) with open(f"{home}/.mpd/mpd.conf") as f: conf0 = f.read().rstrip().splitlines() conf1 = [line.replace("6600",str(port)) for line in conf0] conf1 = [line.replace('"~/.mpd',f'"{mpd_dir}') if not "playlists" in line else line for line in conf1] with open(mpd_conf,"wt") as g: print(f"writing {mpd_conf}") g.write("\n".join(conf1)) g.flush() setproctitle(f"Mpd_{rp}") run(["mpd",mpd_conf]) ```