diff --git a/.gitignore b/.gitignore index 014c313..8aaa973 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ replays -temp_mic_audio.wav \ No newline at end of file +temp_mic_audio.wav +config.json \ No newline at end of file diff --git a/README.md b/README.md index 983fd3e..112335c 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,36 @@ Why not? Try it out. It's fun. Did not see how you died in that round? Just say "replay that" and see the last 30 seconds of your gameplay in a little overlay box that does not disturb your game. +## Configuration + +Before starting, configure the application by creating a `config.json` file: + +1. Copy the example configuration: + ```sh + cp config.example.json config.json + ``` +2. Edit `config.json` to customize your settings: + - **directories.watch_dir**: Where OBS saves replay buffers (e.g., `E:\OBS`) + - **directories.moveto_dir**: Where processed replays are stored (default: `./replays`) + - **server.port**: HTTP server port for serving video files (default: `8000`) + - **server.websocket_port**: WebSocket port for Electron communication (default: `8001`) + - **replay.length_seconds**: Duration of replays in seconds (default: `20`) + - **replay.default_location**: Overlay position (`center`, `top_left`, `bottom_left`, `top_right`) + - **replay.default_size**: Overlay size from 1 (smallest) to 10 (largest) + - **voice_recognition.enabled**: Enable/disable voice commands (default: `false`) + - **hotkey.enabled**: Enable/disable hotkey (default: `true`) + - **hotkey.key**: Hotkey to trigger replays (default: `pagedown`) + - **commands**: Customize voice command phrases for activation, stop, replay last, and clear + ## Start the thing 1. Clone this repository. -2. Install Electron globally (if you don't have it): +2. Configure the application (see Configuration section above). +3. Install Electron globally (if you don't have it): ```sh npm i -g electron ``` -3. Open multiple terminals and run the following commands: +4. Open multiple terminals and run the following commands: 1. **Terminal 1:** Start the Python server that listens for commands and communicates with OBS & the Electron app: ```sh python main.py @@ -22,23 +44,26 @@ Did not see how you died in that round? Just say "replay that" and see the last ```sh npx electron electron-main.js ``` - 3. **Terminal 3:** Serve the replays folder on port 8000: + 3. **Terminal 3:** Serve the replays folder on the configured port (default 8000): ```sh npx serve ./replays -p 8000 ``` ## Usage 1. Make sure OBS is running with a scene that has your game or desktop. -2. Make sure the everything is running. +2. Make sure everything is running. 3. Make sure you have the WebSocket server configured correctly, check the code or change the port in OBS to 4455 and disable authentication. -4. Make sure the Replay Buffer is enabled in OBS and set to the same length as in `main.py` (default 30 seconds), else ffmpeg will clip to the duration set in the `main.py` script. -5. Say "replay that" (you can change the hotword in `main.py`) to save the last X seconds of gameplay. +4. Make sure the Replay Buffer is enabled in OBS and set to the same length as configured in `config.json` (default 20 seconds), else ffmpeg will clip to the duration set in your configuration. +5. Press your configured hotkey (default: Page Down) or say "replay that" to save the last X seconds of gameplay. ## Other commands -- **Activation:** Say "instant replay" to save the last X seconds. -- **Stop:** Say "stop replay" to hide the replay overlay. -- **Replay Last:** Say "replay last" to show the most recent replay. -- **Clear Replays:** Say "clear replays" to delete all saved clips. +- **Hotkey Toggle:** Press your configured hotkey (default: Page Down) to trigger or stop replays +- **Activation (Voice):** Say "instant replay" to save the last X seconds +- **Stop (Voice):** Say "stop replay" to hide the replay overlay +- **Replay Last (Voice):** Say "replay last" to show the most recent replay +- **Clear Replays (Voice):** Say "clear replays" to delete all saved clips + +All voice commands can be customized in `config.json`. ## Requirements - OBS Studio with a working Websocket Server diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..34ce771 --- /dev/null +++ b/config.example.json @@ -0,0 +1,35 @@ +{ + "directories": { + "watch_dir": "E:\\OBS", + "moveto_dir": "./replays" + }, + "server": { + "port": 8000, + "websocket_port": 8001 + }, + "replay": { + "length_seconds": 20, + "default_location": "center", + "default_size": 5 + }, + "voice_recognition": { + "enabled": false, + "sample_rate": 16000, + "duration": 4, + "calibration_duration": 2 + }, + "live_display": { + "enabled": false, + "interval": 0.5 + }, + "commands": { + "activation": ["instant replay", "save replay", "capture replay", "replay that"], + "stop": ["stop replay", "stop video", "hide replay"], + "replay_last": ["replay last", "show last replay", "play last", "replay it again"], + "clear_replays": ["clear replays", "delete all replays", "nuke replays", "remove all clips"] + }, + "hotkey": { + "enabled": true, + "key": "pagedown" + } +} diff --git a/main.py b/main.py index 125d76c..7772911 100644 --- a/main.py +++ b/main.py @@ -30,6 +30,74 @@ except ImportError: psutil = None +def load_config(): + """Load configuration from config.json with fallback to defaults.""" + default_config = { + "directories": { + "watch_dir": r"E:\OBS", + "moveto_dir": "./replays" + }, + "server": { + "port": 8000, + "websocket_port": 8001 + }, + "replay": { + "length_seconds": 20, + "default_location": "center", + "default_size": 5 + }, + "voice_recognition": { + "enabled": False, + "sample_rate": 16000, + "duration": 4, + "calibration_duration": 2 + }, + "live_display": { + "enabled": False, + "interval": 0.5 + }, + "commands": { + "activation": ["instant replay", "save replay", "capture replay", "replay that"], + "stop": ["stop replay", "stop video", "hide replay"], + "replay_last": ["replay last", "show last replay", "play last", "replay it again"], + "clear_replays": ["clear replays", "delete all replays", "nuke replays", "remove all clips"] + }, + "hotkey": { + "enabled": True, + "key": "pagedown" + } + } + + config_path = "config.json" + if os.path.exists(config_path): + try: + with open(config_path, "r") as f: + user_config = json.load(f) + # Merge user config with defaults (deep merge) + def deep_merge(default, user): + result = default.copy() + for key, value in user.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = deep_merge(result[key], value) + else: + result[key] = value + return result + config = deep_merge(default_config, user_config) + print(f"Configuration loaded from {config_path}") + return config + except Exception as e: + print(f"Error loading config.json: {e}. Using default configuration.") + return default_config + else: + print(f"No {config_path} found. Using default configuration.") + print(f"Tip: Copy config.example.json to config.json to customize settings.") + return default_config + + +# Load configuration +config = load_config() + + def is_obs_running(): """Check if OBS Studio is running.""" if not PSUTIL_AVAILABLE: @@ -73,50 +141,31 @@ def set_process_priority(): print(f"Unable to adjust process priority: {exc}") -# Directories -WATCH_DIR = r"E:\OBS" -MOVETO_DIR = os.path.abspath("./replays") +# Extract configuration values +WATCH_DIR = config["directories"]["watch_dir"] +MOVETO_DIR = os.path.abspath(config["directories"]["moveto_dir"]) -# Server Ports -SERVER_PORT = 8000 -WEBSOCKET_PORT = 8001 +SERVER_PORT = config["server"]["port"] +WEBSOCKET_PORT = config["server"]["websocket_port"] -# Replay Settings -REPLAY_LENGTH_SECONDS = 20 -DEFAULT_LOCATION = "center" # options: 'top_left', 'bottom_left', 'top_right', 'center' -DEFAULT_SIZE = 5 # 1 (smallest) to 10 (largest) +REPLAY_LENGTH_SECONDS = config["replay"]["length_seconds"] +DEFAULT_LOCATION = config["replay"]["default_location"] +DEFAULT_SIZE = config["replay"]["default_size"] -# Voice Recognition Settings -VOICE_ENABLED = False -SAMPLE_RATE = 16000 -DURATION = 4 # seconds to record per command attempt -CALIBRATION_DURATION = 2 # seconds for ambient calibration +VOICE_ENABLED = config["voice_recognition"]["enabled"] +SAMPLE_RATE = config["voice_recognition"]["sample_rate"] +DURATION = config["voice_recognition"]["duration"] +CALIBRATION_DURATION = config["voice_recognition"]["calibration_duration"] -# Live Display Settings -LIVE_DISPLAY_ENABLED = False # Enable periodic status updates during replay -LIVE_DISPLAY_INTERVAL = 0.5 # Seconds between each update (e.g., 0.1 = 10 updates per second) +LIVE_DISPLAY_ENABLED = config["live_display"]["enabled"] +LIVE_DISPLAY_INTERVAL = config["live_display"]["interval"] -# Commands -ACTIVATION_COMMANDS = ["instant replay", "save replay", "capture replay", "replay that"] -STOP_COMMANDS = ["stop replay", "stop video", "hide replay"] -REPLAY_LAST_COMMANDS = [ - "replay last", - "show last replay", - "play last", - "replay it again", -] -CLEAR_REPLAYS_COMMANDS = [ - "clear replays", - "delete all replays", - "nuke replays", - "remove all clips", -] +ACTIVATION_COMMANDS = config["commands"]["activation"] +STOP_COMMANDS = config["commands"]["stop"] +REPLAY_LAST_COMMANDS = config["commands"]["replay_last"] +CLEAR_REPLAYS_COMMANDS = config["commands"]["clear_replays"] -# Hotkey Settings -HOTKEY = { - "enabled": True, - "key": "pagedown", # Change to your preferred key -} +HOTKEY = config["hotkey"] # Replay state tracking is_replaying = threading.Event()