Update some scripts, add script for backing up TL2 savegames and using ncmpcpp to play back and manage a mpd-httpd stream
	
		
	
				
					
				
			
							parent
							
								
									bb560c9e93
								
							
						
					
					
						commit
						aa3326e000
					
				
				 5 changed files with 232 additions and 1 deletions
			
			
		@ -0,0 +1,23 @@ | 
				
			|||||||
 | 
					#!/bin/zsh | 
				
			||||||
 | 
					# Back-up Torchlight II savegames | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Path to steam library | 
				
			||||||
 | 
					STEAM_APPS=/media/Data/SteamLibrary/SteamApps | 
				
			||||||
 | 
					# Steam User ID to save, use * to backup all saves | 
				
			||||||
 | 
					STEAM_USER=* | 
				
			||||||
 | 
					# Path to create backups at | 
				
			||||||
 | 
					BACKUP_DIR=~/.backup/TL2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TL2 paths | 
				
			||||||
 | 
					TL2_FOLDER="common/Torchlight II" | 
				
			||||||
 | 
					SAV_FOLDER="my games/runic games/torchlight 2" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					source $DOTFILES/lib/libzsh-utils.zsh | 
				
			||||||
 | 
					cd "$STEAM_APPS/$TL2_FOLDER/$SAV_FOLDER" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					filename="tl2saves_`date +%Y-%m-%d_%H-%M`.tar.xz" | 
				
			||||||
 | 
					msg "Backing up Torchlight II Saves as $filename (in $BACKUP_DIR)" | 
				
			||||||
 | 
					[ -e "$BACKUP_DIR" ] || mkdir -p "$BACKUP_DIR" | 
				
			||||||
 | 
					color 36 tar -cJvf "$BACKUP_DIR/$filename" {,mod}save/${^~STEAM_USER} | 
				
			||||||
 | 
					msg "Done!" | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,170 @@ | 
				
			|||||||
 | 
					#!/usr/bin/python | 
				
			||||||
 | 
					# Use ncmpcpp to play and control a mpd-httpd stream while still being able to control the local volume | 
				
			||||||
 | 
					# (c) 2015 Taeyeon Mori | 
				
			||||||
 | 
					# requires Python 3.4 or higher | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import sys | 
				
			||||||
 | 
					import pty | 
				
			||||||
 | 
					import tty | 
				
			||||||
 | 
					import stat | 
				
			||||||
 | 
					import array | 
				
			||||||
 | 
					import fcntl | 
				
			||||||
 | 
					import signal | 
				
			||||||
 | 
					import asyncio | 
				
			||||||
 | 
					import termios | 
				
			||||||
 | 
					import argparse | 
				
			||||||
 | 
					import contextlib | 
				
			||||||
 | 
					import subprocess | 
				
			||||||
 | 
					import concurrent.futures | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					URL_TEMPLATE = "http://{host}:{http_port}/" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ASYNCIO HACKS ================================================================================== | 
				
			||||||
 | 
					# asyncio is following a great rationale, but at this point in time, there are still some things | 
				
			||||||
 | 
					# missing from it to make it truly useful. Here comes one of 'em: | 
				
			||||||
 | 
					@asyncio.coroutine | 
				
			||||||
 | 
					def async_stdio(loop=None): | 
				
			||||||
 | 
					    # get streams for the standard I/O (stdin and stdout) | 
				
			||||||
 | 
					    if not os.path.sameopenfile(0, 1): | 
				
			||||||
 | 
					        raise RuntimeError("The async_stdio hack only works when both STDIN and STDOUT point to the same TTY/PTS") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if loop is None: | 
				
			||||||
 | 
					        loop = asyncio.get_event_loop() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reader = asyncio.StreamReader() | 
				
			||||||
 | 
					    reader_protocol = asyncio.StreamReaderProtocol(reader) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    writer_transport, writer_protocol = yield from loop.connect_write_pipe(asyncio.streams.FlowControlMixin, os.fdopen(0, 'wb')) | 
				
			||||||
 | 
					    writer = asyncio.StreamWriter(writer_transport, writer_protocol, None, loop) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yield from loop.connect_read_pipe(lambda: reader_protocol, sys.stdin) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return reader, writer | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@asyncio.coroutine | 
				
			||||||
 | 
					def async_pty(pty, loop=None): | 
				
			||||||
 | 
					    # same as above, just with a pty descriptor instead of STD* | 
				
			||||||
 | 
					    if loop is None: | 
				
			||||||
 | 
					        loop = asyncio.get_event_loop() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reader = asyncio.StreamReader() | 
				
			||||||
 | 
					    reader_protocol = asyncio.StreamReaderProtocol(reader) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    writer_transport, writer_protocol = yield from loop.connect_write_pipe(asyncio.streams.FlowControlMixin, os.fdopen(pty, 'wb')) | 
				
			||||||
 | 
					    writer = asyncio.StreamWriter(writer_transport, writer_protocol, None, loop) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yield from loop.connect_read_pipe(lambda: reader_protocol, os.fdopen(pty)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return reader, writer | 
				
			||||||
 | 
					# END ASYNCIO HACKS ============================================================================== | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_args(argv): | 
				
			||||||
 | 
					    parser = argparse.ArgumentParser(prog=argv[0], add_help=False) | 
				
			||||||
 | 
					    parser.add_argument("--help", action="store_true") | 
				
			||||||
 | 
					    parser.add_argument("-h", "--host", default=os.environ.get("MPD_HOST", "localhost")) | 
				
			||||||
 | 
					    parser.add_argument("-p", "--port", default=os.environ.get("MPD_PORT", "6600")) | 
				
			||||||
 | 
					    parser.add_argument("-P", "--http-port", default="8000") | 
				
			||||||
 | 
					    parser.add_argument("-E", "--http-output", default="1") | 
				
			||||||
 | 
					    args = parser.parse_args(argv[1:]) | 
				
			||||||
 | 
					    if args.help: | 
				
			||||||
 | 
					        parser.print_help() | 
				
			||||||
 | 
					        sys.exit(0) | 
				
			||||||
 | 
					    return args | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@asyncio.coroutine | 
				
			||||||
 | 
					def main_coro(args, loop): | 
				
			||||||
 | 
					    # Enable stream | 
				
			||||||
 | 
					    subprocess.check_call(["mpc", "-h", args.host, "-p", args.port, "enable", args.http_output]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Manage stdio | 
				
			||||||
 | 
					    std_reader, std_writer = yield from async_stdio() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Manage vlc | 
				
			||||||
 | 
					    vlc_proc = yield from asyncio.create_subprocess_exec("vlc", "--repeat", URL_TEMPLATE.format(**vars(args)), "-I", "rc", stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | 
				
			||||||
 | 
					    vlc_writer = vlc_proc.stdin | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Manage pty | 
				
			||||||
 | 
					    ptm, pts = pty.openpty() | 
				
			||||||
 | 
					    loop.add_signal_handler(signal.SIGWINCH, propagate_winsize, ptm) | 
				
			||||||
 | 
					    propagate_winsize(ptm) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Manage ncmpc | 
				
			||||||
 | 
					    ptm_reader, ptm_writer = yield from async_pty(ptm) | 
				
			||||||
 | 
					    pty_proc = yield from asyncio.create_subprocess_exec("ncmpcpp", "-h", args.host, "-p", args.port, stdin=pts, stdout=pts, stderr=pts, start_new_session=True, preexec_fn=reopen_tty) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Magic | 
				
			||||||
 | 
					    input_task = asyncio.async(process_input(std_reader, ptm_writer, vlc_writer)) | 
				
			||||||
 | 
					    output_task = asyncio.async(process_output(ptm_reader, std_writer)) | 
				
			||||||
 | 
					    yield from asyncio.wait([input_task, output_task, pty_proc.wait()], return_when=concurrent.futures.FIRST_COMPLETED) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Cleanup | 
				
			||||||
 | 
					    if pty_proc.returncode is None: | 
				
			||||||
 | 
					        pty_proc.terminate() | 
				
			||||||
 | 
					    vlc_proc.terminate() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yield from asyncio.wait([input_task, output_task], timeout=1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loop.remove_signal_handler(signal.SIGWINCH) | 
				
			||||||
 | 
					    os.close(ptm) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def propagate_winsize(fd): | 
				
			||||||
 | 
					    # Notify pty of window size | 
				
			||||||
 | 
					    buf = array.array('h', [0, 0, 0, 0]) | 
				
			||||||
 | 
					    fcntl.ioctl(1, termios.TIOCGWINSZ, buf, True) | 
				
			||||||
 | 
					    fcntl.ioctl(fd, termios.TIOCSWINSZ, buf) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def reopen_tty(): | 
				
			||||||
 | 
					    # reopen tty to make it the controlling tty (see stdlib pty) | 
				
			||||||
 | 
					    open(os.ttyname(1), "wb").close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@asyncio.coroutine | 
				
			||||||
 | 
					def process_input(reader, writer, vlc_writer): | 
				
			||||||
 | 
					    reader_fd = reader._transport.get_extra_info("pipe").fileno() | 
				
			||||||
 | 
					    if True: # placeholder for eventual context manager for termio reset | 
				
			||||||
 | 
					        tty.setraw(reader_fd) | 
				
			||||||
 | 
					        while True: | 
				
			||||||
 | 
					            data = yield from reader.read(512) | 
				
			||||||
 | 
					            if not data: | 
				
			||||||
 | 
					                break | 
				
			||||||
 | 
					            elif data == b'+': | 
				
			||||||
 | 
					                vlc_writer.write(b"volup 2\n") | 
				
			||||||
 | 
					                yield from vlc_writer.drain() | 
				
			||||||
 | 
					            elif data == b'-': | 
				
			||||||
 | 
					                vlc_writer.write(b"voldown 2\n") | 
				
			||||||
 | 
					                yield from vlc_writer.drain() | 
				
			||||||
 | 
					            #elif data == b'\x03': # CTRL-C, RAW mode | 
				
			||||||
 | 
					            #    raise KeyboardInterrupt() | 
				
			||||||
 | 
					            else: | 
				
			||||||
 | 
					                writer.write(data) | 
				
			||||||
 | 
					                yield from writer.drain() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@asyncio.coroutine | 
				
			||||||
 | 
					def process_output(reader, writer): | 
				
			||||||
 | 
					    while True: | 
				
			||||||
 | 
					        data = yield from reader.read(256) | 
				
			||||||
 | 
					        if data: | 
				
			||||||
 | 
					            writer.write(data) | 
				
			||||||
 | 
					            yield from writer.drain() | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					            break | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					    args = parse_args(sys.argv) | 
				
			||||||
 | 
					    loop = asyncio.get_event_loop() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mode = termios.tcgetattr(0) | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					        sys.exit(loop.run_until_complete(main_coro(args, loop))) | 
				
			||||||
 | 
					    finally: | 
				
			||||||
 | 
					        termios.tcsetattr(0, termios.TCSADRAIN, mode) | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,37 @@ | 
				
			|||||||
 | 
					#!/bin/sh | 
				
			||||||
 | 
					# connect to the mpd httpd and open ncmpcpp | 
				
			||||||
 | 
					# Requirements: vlc, mpc, ncmpcpp | 
				
			||||||
 | 
					# Use MPD_HOST, MPD_PORT, MPD_HTTP_SINK, MPD_HTTP_PORT environment variables instead of arguments | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					: ${MPD_HOST:=127.0.0.1} ${MPD_PORT:=6600} | 
				
			||||||
 | 
					: ${MPD_HTTP_SINK:=1} ${MPD_HTTP_PORT:=8080} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export MPD_HOST | 
				
			||||||
 | 
					export MPD_PORT | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Find programs | 
				
			||||||
 | 
					mpc=`which mpc 2>/dev/null` | 
				
			||||||
 | 
					vlc=`which vlc 2>/dev/null` | 
				
			||||||
 | 
					client=`which ncmpcpp 2>/dev/null || which ncmpc 2>/dev/null` | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -z "$mpc" ]; then | 
				
			||||||
 | 
						echo "ERROR: mpc (MusicPlayerClient) not found on the system" | 
				
			||||||
 | 
					elif [ -z "$vlc" ]; then | 
				
			||||||
 | 
						echo "ERROR: VLC is required for local stream playback" | 
				
			||||||
 | 
					elif [ -z "$client" ]; then | 
				
			||||||
 | 
						echo "ERROR: No MPD client installed (ncmpcpp or ncmpc)" | 
				
			||||||
 | 
					else | 
				
			||||||
 | 
						# Enable Stream | 
				
			||||||
 | 
						mpc enable $MPC_HTTP_SINK | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Play Stream (have vlc on fd:3) | 
				
			||||||
 | 
						vlc --repeat http://$MPD_HOST:$MPD_HTTP_PORT/mpd.ogg -I dummy 2>/dev/null & | 
				
			||||||
 | 
						vlc_pid=$! | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Launch client in pty | 
				
			||||||
 | 
						"$client" -h $MPD_HOST -p $MPD_PORT | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Stop stream playback | 
				
			||||||
 | 
						kill $vlc_pid | 
				
			||||||
 | 
					fi | 
				
			||||||
 | 
					
 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue