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