diff --git a/scripts/NOTES.md b/scripts/NOTES.md new file mode 100644 index 0000000..305bd68 --- /dev/null +++ b/scripts/NOTES.md @@ -0,0 +1,35 @@ +# Beat Saber Playlist Scripts + +Tools for managing Beat Saber playlists and custom maps across Windows and other systems. + +## Map Installation + +To copy a custom map to Beat Saber: +```powershell +Copy-Item -Path "map_folder" -Destination "C:\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\CustomLevels\" -Recurse +``` + +## Playlist Sync Script +`sabersync.py` is a utility script that: +- Syncs `.bplist` files between systems +- Archives old/deleted playlists +- Optionally syncs Beat Saber video recordings + +Usage: +```bash +./sabersync.py [--video] [--help] +``` + +Options: +- `--video`: Sync video recordings from the remote system +- `--help`: Show help message + +The script expects: +- SSH access to a Windows machine with alias `winroar` +- Beat Saber installed at standard Steam paths +- Playlists in `BSManager/BSInstances/1.39.1/Playlists/` + +When run with `--video`, it will: +1. Copy videos from the remote system +2. Wait for confirmation +3. Clean up remote video files after transfer \ No newline at end of file diff --git a/scripts/sabersync.py b/scripts/sabersync.py new file mode 100755 index 0000000..0b07e6b --- /dev/null +++ b/scripts/sabersync.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# Helper script to backup playlists and copy new ones over + +import argparse +import subprocess +import os +import shutil +from pathlib import Path +import sys +import shlex + +def usage(): + print("Usage: {} [--video]".format(sys.argv[0])) + sys.exit(1) + +def run_command(command, capture_output=False, text=True): + try: + result = subprocess.run(command, shell=True, check=True, capture_output=capture_output, text=text) + return result.stdout if capture_output else None + except subprocess.CalledProcessError as e: + print(f"Command '{command}' failed with exit code {e.returncode}") + sys.exit(e.returncode) + +def main(): + parser = argparse.ArgumentParser(description="Synchronize Beat Saber playlists and optionally videos.", add_help=False) + parser.add_argument('-v', '--video', action='store_true', help='Enable video synchronization') + parser.add_argument('-h', '--help', action='store_true', help='Show help message and exit') + args = parser.parse_args() + + if args.help: + usage() + + COPY_VIDEOS = args.video + + # Define paths + REMOTE_PLAYLISTS = "BSManager/BSInstances/1.39.1/Playlists/" + REMOTE_PLAYLISTS_WIN = "BSManager\\BSInstances\\1.39.1\\Playlists" # For Windows SSH commands + LOCAL_ARCHIVE_DIR = Path.home() / "archive" / "beatsaber-playlists" + LOCAL_ARCHIVE_SUBDIR = LOCAL_ARCHIVE_DIR / "archive" + LOCAL_BPLIST_DIR = Path.cwd() + + # Ensure archive subdirectory exists + LOCAL_ARCHIVE_SUBDIR.mkdir(parents=True, exist_ok=True) + + # Check for local *.bplist files and sync to remote using scp + bplist_files = list(LOCAL_BPLIST_DIR.glob("*.bplist")) + if bplist_files: + print("Starting synchronization of .bplist files to remote using scp...") + # Build file string with proper quoting using shlex.quote, so wildcard expansion isn't needed + files_str = " ".join(shlex.quote(str(file)) for file in bplist_files) + scp_sync_upload = f"scp {files_str} winroar:\"{REMOTE_PLAYLISTS}\"" + run_command(scp_sync_upload) + # Remove local *.bplist files after successful transfer + for f in bplist_files: + f.unlink() + print(f"Removed local file: {f.name}") + + # Fetch list of remote *.bplist files + print("Fetching list of remote .bplist files...") + ssh_command = f'ssh winroar "cd {REMOTE_PLAYLISTS_WIN} && dir /b *.bplist"' + remote_bplist = run_command(ssh_command, capture_output=True) + remote_bplist_files = set(remote_bplist.strip().split('\n')) if remote_bplist else set() + + # Fetch list of local archive *.bplist files + print("Fetching list of local archived .bplist files...") + local_archive_files = set(f.name for f in LOCAL_ARCHIVE_DIR.glob("*.bplist")) + + # Determine deleted files (present locally but not remotely) + deleted_files = local_archive_files - remote_bplist_files + if deleted_files: + print("Identifying deleted playlists...") + for file in deleted_files: + local_file = LOCAL_ARCHIVE_DIR / file + destination_file = LOCAL_ARCHIVE_SUBDIR / file + if local_file.exists(): + if destination_file.exists(): + destination_file.unlink() # Remove existing file to allow overwrite + print(f"Overwriting existing archived file: {file}") + shutil.move(str(local_file), str(destination_file)) + print(f"Archived deleted playlist: {file}") + else: + print("No deleted playlists found.") + + # Sync remote *.bplist to local archive directory + print("Downloading current remote .bplist files to local archive...") + scp_sync_download = ( + f"scp winroar:\"{REMOTE_PLAYLISTS}*.bplist\" \"{LOCAL_ARCHIVE_DIR}/\"" + ) + run_command(scp_sync_download) + print("Download complete.") + + # Optional video synchronization + if COPY_VIDEOS: + print("Starting video synchronization with scp...") + + # Define remote and local video paths + REMOTE_VIDEOS = "/Users/pleb/Videos/Beat Saber/" + LOCAL_DOWNLOADS = Path.home() / "Downloads" / "Beat Saber" + + # Ensure local downloads directory exists + LOCAL_DOWNLOADS.mkdir(parents=True, exist_ok=True) + + # Construct scp command to copy videos from remote to local Downloads folder + scp_sync_videos = ( + f"scp -r winroar:\"{REMOTE_VIDEOS}\" \"{LOCAL_DOWNLOADS}\"" + ) + run_command(scp_sync_videos) + print("Video files synchronized successfully.") + + input("Press [Enter] to delete videos on winroar.") + + # Delete videos on remote after confirmation + ssh_delete_command = f'ssh winroar \'rm -rf "{REMOTE_VIDEOS}"\'' + run_command(ssh_delete_command) + print("Remote video directory deleted.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/sabersync.sh b/scripts/sabersync.sh new file mode 100755 index 0000000..ffe6816 --- /dev/null +++ b/scripts/sabersync.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -e + +# Function to display usage +usage() { + echo "Usage: $0 [--video]" + exit 1 +} + +# Parse command-line arguments +COPY_VIDEOS=false + +while [[ "$#" -gt 0 ]]; do + case $1 in + -v|--video) + COPY_VIDEOS=true + shift + ;; + -h|--help) + usage + ;; + *) + echo "Unknown parameter passed: $1" + usage + ;; + esac +done + +if [ -n "$(find . -maxdepth 1 -type f -name "*.bplist" -print -quit)" ]; then + scp ./*.bplist winroar:BSManager/BSInstances/1.39.1/Playlists/ + rm -v ./*.bplist +fi + +## +## Playlist Archive +## + +# Define remote and local directories for playlist synchronization +REMOTE_PLAYLISTS="BSManager/BSInstances/1.39.1/Playlists/" +LOCAL_ARCHIVE_DIR=~/archive/beatsaber-playlists/ +LOCAL_ARCHIVE_SUBDIR=~/archive/beatsaber-playlists/archive/ +PREVIOUS_LIST=~/archive/beatsaber-playlists/previous_bplist_files.txt + +# Ensure archive subdirectory exists +mkdir -p "$LOCAL_ARCHIVE_SUBDIR" + +# Fetch current remote *.bplist files +ssh winroar "cd \"${REMOTE_PLAYLISTS}\" && dir /B *.bplist" > current_bplist_files.txt + +# Compare with previous list to find deleted files +if [[ -f "$PREVIOUS_LIST" ]]; then + deleted_files=$(comm -23 <(sort "$PREVIOUS_LIST") <(sort current_bplist_files.txt)) + for file in $deleted_files; do + filename=$(basename "$file") + if [[ -f "${LOCAL_ARCHIVE_DIR}${filename}" ]]; then + mv "${LOCAL_ARCHIVE_DIR}${filename}" "$LOCAL_ARCHIVE_SUBDIR" + echo "Archived deleted playlist: $filename" + fi + done +fi + +# Update the previous list +mv current_bplist_files.txt "$PREVIOUS_LIST" + +# Sync playlist files from remote to local +scp -q winroar:"${REMOTE_PLAYLISTS}*.bplist" "$LOCAL_ARCHIVE_DIR" + +# Optional video synchronization +if $COPY_VIDEOS; then + echo "Starting video synchronization..." + + # Copy videos from remote to local Downloads folder + scp -r winroar:"Videos/Beat Saber/" "$HOME/Downloads/" + + echo "Video files copied successfully." + + echo "Press [Enter] to delete videos on winroar." + read -r + + # Delete videos on remote after confirmation + ssh winroar 'rmdir Videos/Beat\ Saber /q /s' + + echo "Remote video directory deleted." +fi