From d9543e4b607aff39fd29ecf80c4bea53a9c29465 Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 13 Feb 2026 00:01:10 -0800 Subject: [PATCH 1/4] update --- projects/scripts/whisper_record_transcribe.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/projects/scripts/whisper_record_transcribe.py b/projects/scripts/whisper_record_transcribe.py index 79e4e7f..38ba47a 100755 --- a/projects/scripts/whisper_record_transcribe.py +++ b/projects/scripts/whisper_record_transcribe.py @@ -444,6 +444,11 @@ def _type_with_tool(text: str) -> None: raise RuntimeError("No typing tool found. Install one of: wtype, ydotool, xdotool.") +def _normalize_text_for_typing(text: str) -> str: + # Prevent simulated Enter key presses from transcript line breaks. + return " ".join(text.split()) + + def _emit_text(text: str, args: argparse.Namespace, notifier: Notifier) -> int: if args.output == "print": print(text) @@ -451,7 +456,7 @@ def _emit_text(text: str, args: argparse.Namespace, notifier: Notifier) -> int: return 0 try: - _type_with_tool(text) + _type_with_tool(_normalize_text_for_typing(text)) except Exception as exc: print(f"Failed to simulate typing: {exc}", file=sys.stderr) notifier.send("Typing error", str(exc), timeout_ms=2500) From e3b0fdbddb02442166a663965e5c094786f0b61f Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 13 Feb 2026 00:01:25 -0800 Subject: [PATCH 2/4] update --- .config/hypr/hyprland.conf | 1 + .config/hypr/keybindings.conf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/hypr/hyprland.conf b/.config/hypr/hyprland.conf index 6ef8cf0..a7807bb 100644 --- a/.config/hypr/hyprland.conf +++ b/.config/hypr/hyprland.conf @@ -78,6 +78,7 @@ exec-once = uwsm app -sb -t service -- nm-applet exec-once = uwsm app -sb -t service -- waybar -c ~/.config/waybar/catppuccin-macchiato/config.jsonc -s ~/.config/waybar/catppuccin-macchiato/style.css exec-once = uwsm app -sb -t service -- hyprsunset exec-once = uwsm app -sb -t service -- /usr/lib/polkit-kde-authentication-agent-1 +exec-once = uwsm app -sb -t service -- gnome-keyring-daemon --start --components=secrets,ssh,pkcs11 # exec-once = uwsm app -sb -t service -- variety exec-once = ~/.local/bin/aria # exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP diff --git a/.config/hypr/keybindings.conf b/.config/hypr/keybindings.conf index 68e037a..b62ebdc 100644 --- a/.config/hypr/keybindings.conf +++ b/.config/hypr/keybindings.conf @@ -95,7 +95,7 @@ bind = $mainMod SHIFT, v, exec, uwsm app -sb -- rofi-rbw # bind = $mainMod, w, exec, rofi -show window -theme $HOME/.config/rofi/launchers/type-2/style-2.rasi -dpi 96 -theme-str 'window {width: 35%;}' bind = $mainMod SHIFT, w, exec, "$HOME/.config/rofi/scripts/rofi-wallpaper.sh" bind = $mainMod SHIFT, d, exec, "$HOME/.config/rofi/scripts/rofi-docs.sh" -bind = SUPER, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh" +bind = SUPER SHIFT, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh" bind = SUPER, t, exec, "$HOME/.config/rofi/scripts/rofi-launch-texthooker-steam.sh" bind = $mainMod SHIFT, t, exec, "$HOME/projects/scripts/popup-ai-translator.py" bind = SUPER SHIFT, g, exec, "$HOME/.config/rofi/scripts/rofi-vn-helper.sh" From 362c7863ff899eb25c685ba81fb84d1aabec0bca Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 15 Feb 2026 02:52:37 -0800 Subject: [PATCH 3/4] update --- .config/SubMiner/config.jsonc##os.Darwin | 131 +++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100755 .config/SubMiner/config.jsonc##os.Darwin diff --git a/.config/SubMiner/config.jsonc##os.Darwin b/.config/SubMiner/config.jsonc##os.Darwin new file mode 100755 index 0000000..e2bbeb9 --- /dev/null +++ b/.config/SubMiner/config.jsonc##os.Darwin @@ -0,0 +1,131 @@ +{ + "keybindings": [], + "shortcuts": { + "copySubtitle": "CommandOrControl+C", + "copySubtitleMultiple": "CommandOrControl+Shift+C", + "updateLastCardFromClipboard": "CommandOrControl+V", + "triggerFieldGrouping": "CommandOrControl+G", + "triggerSubsync": "CommandOrControl+Alt+S", + "mineSentence": "CommandOrControl+S", + "mineSentenceMultiple": "CommandOrControl+Shift+S", + "multiCopyTimeoutMs": 3000, + "toggleSecondarySub": "CommandOrControl+Shift+V", + "markAudioCard": "CommandOrControl+Shift+A", + "openRuntimeOptions": "CommandOrControl+Shift+O", + "toggleVisibleOverlayGlobal": "Alt+Shift+O", + "toggleInvisibleOverlayGlobal": "Alt+Shift+I", + }, + "auto_start_overlay": false, + "bind_visible_overlay_to_mpv_sub_visibility": false, + "texthooker": { + "openBrowser": false, + }, + "websocket": { + "enabled": "auto", + "port": 6677, + }, + "ankiConnect": { + "enabled": true, + "url": "http://127.0.0.1:8765", + "deck": "Minecraft", + "pollingRate": 500, + "fields": { + "audio": "ExpressionAudio", + "image": "Picture", + "sentence": "Sentence", + "miscInfo": "MiscInfo", + "translation": "SelectionText", + }, + "openRouter": { + "enabled": true, + "alwaysUseAiTranslation": true, + "apiKey": "", + "model": "openai/gpt-oss-120b:free", + "baseUrl": "https://openrouter.ai/api/v1", + "sourceLanguage": "Japanese", + "systemPrompt": "You are a translation engine for translating Japanese into natural-sounding, context-aware English. Return only the translated text with no extra explanations or commentary. The translation must preserve the original tone and intent of the source. If the input is not in the target language, translate it to the target language. If the input is already in the target language, return it as is.", + }, + "media": { + "generateAudio": true, + "generateImage": true, + "imageType": "avif", + "imageFormat": "webp", + "animatedFps": 24, + "animatedMaxWidth": 640, + "animatedMaxHeight": null, + "animatedCrf": 35, + "audioPadding": 0.5, + "fallbackDuration": 3, + }, + "behavior": { + "overwriteAudio": false, + "overwriteImage": true, + "mediaInsertMode": "append", + "highlightWord": true, + "notificationType": "system", + "showNotificationOnUpdate": true, + "autoUpdateNewCards": false, + }, + "nPlusOne": { + "decks": ["Minecraft", "Kaishi 1.5k"], + "highlightEnabled": true, + "refreshMinutes": 60, + "matchMode": "headword", + }, + "metadata": { + "pattern": "[SubMiner] %f (%t)", + }, + "isLapis": { + "enabled": true, + "sentenceCardModel": "Lapis Morph", + "sentenceCardSentenceField": "Sentence", + "sentenceCardAudioField": "SentenceAudio", + }, + "isKiku": { + "enabled": true, + "fieldGrouping": "manual", + "deleteDuplicateInAuto": true, + }, + }, + "secondarySub": { + "autoLoadSecondarySub": true, + "secondarySubLanguages": ["en", "eng"], + }, + "subsync": { + "defaultMode": "manual", + "alass_path": "~/.local/bin/alass-cli", + "ffsubsync_path": "~/.local/bin/ffsubsync", + "ffmpeg_path": "/opt/homebrew/ffmpeg/bin/ffmpeg", + }, + "subtitleStyle": { + "fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif", + "fontSize": 35, + "fontColor": "#cad3f5", + "fontWeight": "normal", + "fontStyle": "normal", + "backgroundColor": "rgb(30, 32, 48, 0.88)", + "secondary": { + "fontSize": 24, + "fontColor": "#cad3f5", + "backgroundColor": "transparent", + }, + }, + "jimaku": { + // "apiKey": "YOUR_API_KEY", + // or use a command that outputs the key: + "apiKeyCommand": "cat ~/.jimaku-api-key", + "apiBaseUrl": "https://jimaku.cc", + "languagePreference": "ja", + "maxEntryResults": 10, + }, + "invisibleOverlay": { + // "platform-default" => hidden on Wayland, visible elsewhere + // other values: "visible", "hidden" + "startupVisibility": "platform-default", + }, + "youtubeSubgen": { + "mode": "automatic", // automatic | preprocess | off + "whisperBin": "/usr/bin/whisper-cli", + "whisperModel": "~/models/whisper.cpp/ggml-small.bin", + }, +} From 7db8a5908a8847f850f1430d7b63f768283d9c8e Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 15 Feb 2026 21:13:57 -0800 Subject: [PATCH 4/4] udpate --- .bash_aliases | 3 +- .claude/settings.json##os.Linux | 12 +- .config/SubMiner/config.jsonc##os.Linux | 131 ++++++++++++++++++ .config/hypr/keybindings.conf | 2 +- .config/mpv-modules/ani-skip | 1 + .config/opencode/opencode.json | 172 ++---------------------- .config/tmux/tmux.conf | 2 + .gitmodules | 3 + .zsh/.zshrc##default | 8 ++ projects/scripts/mpv-add.sh | 76 +++++++++-- projects/scripts/rmpv | 85 +++++++++++- 11 files changed, 316 insertions(+), 179 deletions(-) create mode 100644 .config/SubMiner/config.jsonc##os.Linux create mode 160000 .config/mpv-modules/ani-skip diff --git a/.bash_aliases b/.bash_aliases index 302e6b8..9f68795 100644 --- a/.bash_aliases +++ b/.bash_aliases @@ -127,4 +127,5 @@ alias hypr='cd ~/.config/hypr && vim ~/.config/hypr/hyprland.conf && cd -' alias wlc='wl-copy' alias wlp='wl-paste' alias vn32='WINEPREFIX=/home/sudacode/S/lutris/wineprefix32 WINEARCH=win32' -alias impv='mpv --profile=immersion' +alias impv='mpv --profile=subminer' +alias smpv='mpv --profile=subminer' diff --git a/.claude/settings.json##os.Linux b/.claude/settings.json##os.Linux index 2b8d257..ad747a9 100644 --- a/.claude/settings.json##os.Linux +++ b/.claude/settings.json##os.Linux @@ -39,19 +39,23 @@ "pyright-lsp@claude-plugins-official": true, "typescript-lsp@claude-plugins-official": true, "clangd-lsp@claude-plugins-official": true, - "claude-mem@thedotmack": true, "frontend-design@claude-plugins-official": true, "code-review@claude-plugins-official": true, "code-simplifier@claude-plugins-official": true, - "playwright@claude-plugins-official": true + "playwright@claude-plugins-official": true, + "superpowers@claude-plugins-official": true }, "sandbox": { "enabled": false, "autoAllowBashIfSandboxed": true, "network": { - "allowUnixSockets": ["/var/run/docker.sock"], + "allowUnixSockets": [ + "/var/run/docker.sock" + ], "allowLocalBinding": true }, - "excludedCommands": ["docker"] + "excludedCommands": [ + "docker" + ] } } diff --git a/.config/SubMiner/config.jsonc##os.Linux b/.config/SubMiner/config.jsonc##os.Linux new file mode 100644 index 0000000..316c965 --- /dev/null +++ b/.config/SubMiner/config.jsonc##os.Linux @@ -0,0 +1,131 @@ +{ + "keybindings": [], + "shortcuts": { + "copySubtitle": "CommandOrControl+C", + "copySubtitleMultiple": "CommandOrControl+Shift+C", + "updateLastCardFromClipboard": "CommandOrControl+V", + "triggerFieldGrouping": "CommandOrControl+G", + "triggerSubsync": "CommandOrControl+Alt+S", + "mineSentence": "CommandOrControl+S", + "mineSentenceMultiple": "CommandOrControl+Shift+S", + "multiCopyTimeoutMs": 3000, + "toggleSecondarySub": "CommandOrControl+Shift+V", + "markAudioCard": "CommandOrControl+Shift+A", + "openRuntimeOptions": "CommandOrControl+Shift+O", + "toggleVisibleOverlayGlobal": "Alt+Shift+O", + "toggleInvisibleOverlayGlobal": "Alt+Shift+I", + }, + "auto_start_overlay": false, + "bind_visible_overlay_to_mpv_sub_visibility": false, + "texthooker": { + "openBrowser": false, + }, + "websocket": { + "enabled": "auto", + "port": 6677, + }, + "ankiConnect": { + "enabled": true, + "url": "http://127.0.0.1:8765", + "deck": "Minecraft", + "pollingRate": 500, + "fields": { + "audio": "ExpressionAudio", + "image": "Picture", + "sentence": "Sentence", + "miscInfo": "MiscInfo", + "translation": "SelectionText", + }, + "openRouter": { + "enabled": true, + "alwaysUseAiTranslation": true, + "apiKey": "", + "model": "openai/gpt-oss-120b:free", + "baseUrl": "https://openrouter.ai/api/v1", + "sourceLanguage": "Japanese", + "systemPrompt": "You are a translation engine for translating Japanese into natural-sounding, context-aware English. Return only the translated text with no extra explanations or commentary. The translation must preserve the original tone and intent of the source. If the input is not in the target language, translate it to the target language. If the input is already in the target language, return it as is.", + }, + "media": { + "generateAudio": true, + "generateImage": true, + "imageType": "avif", + "imageFormat": "webp", + "animatedFps": 24, + "animatedMaxWidth": 640, + "animatedMaxHeight": null, + "animatedCrf": 35, + "audioPadding": 0.5, + "fallbackDuration": 3, + }, + "behavior": { + "overwriteAudio": false, + "overwriteImage": true, + "mediaInsertMode": "append", + "highlightWord": true, + "notificationType": "system", + "showNotificationOnUpdate": true, + "autoUpdateNewCards": false, + }, + "nPlusOne": { + "decks": ["Minecraft", "Kaishi 1.5k"], + "highlightEnabled": true, + "refreshMinutes": 60, + "matchMode": "headword", + }, + "metadata": { + "pattern": "[SubMiner] %f (%t)", + }, + "isLapis": { + "enabled": true, + "sentenceCardModel": "Lapis Morph", + "sentenceCardSentenceField": "Sentence", + "sentenceCardAudioField": "SentenceAudio", + }, + "isKiku": { + "enabled": true, + "fieldGrouping": "manual", + "deleteDuplicateInAuto": true, + }, + }, + "secondarySub": { + "autoLoadSecondarySub": true, + "secondarySubLanguages": ["en", "eng"], + }, + "subsync": { + "defaultMode": "manual", + "alass_path": null, + "ffsubsync_path": null, + "ffmpeg_path": null, + }, + "subtitleStyle": { + "fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif", + "fontSize": 35, + "fontColor": "#cad3f5", + "fontWeight": "normal", + "fontStyle": "normal", + "backgroundColor": "rgb(30, 32, 48, 0.88)", + "secondary": { + "fontSize": 24, + "fontColor": "#cad3f5", + "backgroundColor": "transparent", + }, + }, + "jimaku": { + // "apiKey": "YOUR_API_KEY", + // or use a command that outputs the key: + "apiKeyCommand": "cat ~/.jimaku-api-key", + "apiBaseUrl": "https://jimaku.cc", + "languagePreference": "ja", + "maxEntryResults": 10, + }, + "invisibleOverlay": { + // "platform-default" => hidden on Wayland, visible elsewhere + // other values: "visible", "hidden" + "startupVisibility": "platform-default", + }, + "youtubeSubgen": { + "mode": "automatic", // automatic | preprocess | off + "whisperBin": "/usr/bin/whisper-cli", + "whisperModel": "~/models/whisper.cpp/ggml-small.bin", + }, +} diff --git a/.config/hypr/keybindings.conf b/.config/hypr/keybindings.conf index b62ebdc..c6f74b9 100644 --- a/.config/hypr/keybindings.conf +++ b/.config/hypr/keybindings.conf @@ -133,7 +133,7 @@ bind = $mainMod, z, exec, uwsm app -sb -- zen-browser bind = $mainMod SHIFT, s, exec , rofi -show ssh -theme "$HOME/.config/rofi/launchers/type-2/style-2.rasi" -terminal -theme-str 'window{width: 25%;} listview {columns: 1; lines: 10;}' ghostty -ssh-command "ghostty --initial-command='TERM=kitty ssh {host}'" # reload monitors -bind = $mainMod SHIFT, R, exec, hyprctl dispatch dpms off && sleep 1 && hyprctl dispatch dpms on +bind = CTRL $mainMod SHIFT, R, exec, hyprctl dispatch dpms off && sleep 1 && hyprctl dispatch dpms on # Disable keybinds with one master keybind # https://wiki.hypr.land/0.49.0/Configuring/Uncommon-tips--tricks/#disabling-keybinds-with-one-master-keybind diff --git a/.config/mpv-modules/ani-skip b/.config/mpv-modules/ani-skip new file mode 160000 index 0000000..12b4960 --- /dev/null +++ b/.config/mpv-modules/ani-skip @@ -0,0 +1 @@ +Subproject commit 12b4960ecd34082a47ae4387d802b0a2847ec0a2 diff --git a/.config/opencode/opencode.json b/.config/opencode/opencode.json index 6d91777..8d3b32b 100644 --- a/.config/opencode/opencode.json +++ b/.config/opencode/opencode.json @@ -3,142 +3,16 @@ "plugin": [ "opencode-openai-codex-auth", "opencode-antigravity-auth@beta", - "@mohak34/opencode-notifier@latest", + "@mohak34/opencode-notifier@latest" ], + "mcp": { + "backlog": { + "type": "local", + "command": ["backlog", "mcp", "start"], + "enabled": true + } + }, "provider": { - "openai": { - "name": "OpenAI", - "options": { - "reasoningEffort": "medium", - "reasoningSummary": "auto", - "textVerbosity": "medium", - "include": [ - "reasoning.encrypted_content" - ], - "store": false - }, - "models": { - "gpt-5.2": { - "name": "GPT 5.2 (OAuth)", - "limit": { - "context": 272000, - "output": 128000 - }, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "variants": { - "none": { - "reasoningEffort": "none", - "reasoningSummary": "auto", - "textVerbosity": "medium" - }, - "low": { - "reasoningEffort": "low", - "reasoningSummary": "auto", - "textVerbosity": "medium" - }, - "medium": { - "reasoningEffort": "medium", - "reasoningSummary": "auto", - "textVerbosity": "medium" - }, - "high": { - "reasoningEffort": "high", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - }, - "xhigh": { - "reasoningEffort": "xhigh", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - } - } - }, - "gpt-5.2-codex": { - "name": "GPT 5.2 Codex (OAuth)", - "limit": { - "context": 272000, - "output": 128000 - }, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "variants": { - "low": { - "reasoningEffort": "low", - "reasoningSummary": "auto", - "textVerbosity": "medium" - }, - "medium": { - "reasoningEffort": "medium", - "reasoningSummary": "auto", - "textVerbosity": "medium" - }, - "high": { - "reasoningEffort": "high", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - }, - "xhigh": { - "reasoningEffort": "xhigh", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - } - } - }, - "gpt-5.2-codex-max": { - "name": "GPT 5.2 Codex Max (OAuth)", - "limit": { - "context": 272000, - "output": 128000 - }, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "variants": { - "low": { - "reasoningEffort": "low", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - }, - "medium": { - "reasoningEffort": "medium", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - }, - "high": { - "reasoningEffort": "high", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - }, - "xhigh": { - "reasoningEffort": "xhigh", - "reasoningSummary": "detailed", - "textVerbosity": "medium" - } - } - } - } - }, "google": { "name": "Google", "models": { @@ -151,14 +25,8 @@ "output": 65535 }, "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] + "input": ["text", "image", "pdf"], + "output": ["text"] } }, "antigravity-gemini-3-pro-low": { @@ -170,14 +38,8 @@ "output": 65535 }, "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] + "input": ["text", "image", "pdf"], + "output": ["text"] } }, "antigravity-gemini-3-flash": { @@ -188,14 +50,8 @@ "output": 65536 }, "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] + "input": ["text", "image", "pdf"], + "output": ["text"] } } } diff --git a/.config/tmux/tmux.conf b/.config/tmux/tmux.conf index 0352340..36f292a 100644 --- a/.config/tmux/tmux.conf +++ b/.config/tmux/tmux.conf @@ -85,6 +85,8 @@ bind j select-pane -D bind k select-pane -U bind l select-pane -R +bind r source-file ~/.tmux.conf \; display "Reloaded tmux config" + set-window-option -g mode-keys vi set -g mode-keys vi set -g status-keys vi diff --git a/.gitmodules b/.gitmodules index 7003048..8385783 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule ".config/mpv-modules/animecards"] path = .config/mpv-modules/animecards url = git@github.com:ksyasuda/Anacreon-Script +[submodule ".config/mpv-modules/ani-skip"] + path = .config/mpv-modules/ani-skip + url = https://github.com/synacktraa/ani-skip.git diff --git a/.zsh/.zshrc##default b/.zsh/.zshrc##default index 01d7dee..b73332f 100644 --- a/.zsh/.zshrc##default +++ b/.zsh/.zshrc##default @@ -102,3 +102,11 @@ zle -N bracketed-paste bracketed-paste-magic alias claude-mem='bun "/home/sudacode/.claude/plugins/marketplaces/thedotmack/plugin/scripts/worker-service.cjs"' fpath=(/home/sudacode/.zsh/completions $fpath) autoload -Uz compinit && compinit + +# pnpm +export PNPM_HOME="/home/sudacode/.local/share/pnpm" +case ":$PATH:" in + *":$PNPM_HOME:"*) ;; + *) export PATH="$PNPM_HOME:$PATH" ;; +esac +# pnpm end diff --git a/projects/scripts/mpv-add.sh b/projects/scripts/mpv-add.sh index 9a9a760..bcf867d 100755 --- a/projects/scripts/mpv-add.sh +++ b/projects/scripts/mpv-add.sh @@ -2,28 +2,82 @@ set -Eeuo pipefail -URL="${1:-$(wl-paste -p)}" -MPV_SOCKET=/tmp/mpvsocket +URL="${1:-}" +MPV_SOCKET="/tmp/mpvsocket" ICON_PATH="$HOME/.local/share/icons/Magna-Glassy-Dark-Icons/apps/48/mpv.svg" TITLE="mpv-add.sh" +notify() { + local message="$1" + notify-send -i "$ICON_PATH" "$TITLE" "$message" +} + +trim() { + printf '%s' "$1" | sed 's/^[[:space:]]\+//;s/[[:space:]]\+$//' +} + +play_direct() { + mpv -- "$URL" &> /dev/null & +} + +wait_for_socket() { + local timeout_ms=2000 + local waited=0 + while ((waited < timeout_ms)); do + if [[ -S "$MPV_SOCKET" ]]; then + return 0 + fi + sleep 0.05 + waited=$((waited + 50)) + done + return 1 +} + +is_valid_input() { + local input="$1" + + if [[ -f "$input" ]]; then + return 0 + fi + + if ! command -v yt-dlp > /dev/null 2>&1; then + return 1 + fi + + if yt-dlp --simulate "$input" > /dev/null 2>&1; then + return 0 + fi + + return 1 +} + +if [[ -z "$URL" ]] && command -v wl-paste > /dev/null 2>&1; then + URL="$(wl-paste -p || true)" +fi + +URL="$(trim "$URL")" + if [[ -z "$URL" ]]; then - notify-send -i "$ICON_PATH" "$TITLE" "No URL provided" + notify "No URL provided" exit 1 fi -if ! [[ -f "$URL" ]] && ! yt-dlp --simulate "$URL"; then - notify-send -i "$ICON_PATH" "$TITLE" "Invalid URL" +if ! is_valid_input "$URL"; then + notify "Invalid input: provide a local file path or a yt-dlp supported URL" exit 1 fi if ! pgrep -x mpv &> /dev/null; then - mpv "$URL" &> /dev/null & - notify-send -i "$ICON_PATH" "$TITLE" "Playing $URL" + rm -f "$MPV_SOCKET" + mpv --input-ipc-server="$MPV_SOCKET" -- "$URL" &> /dev/null & + notify "Playing $URL" else - if echo "{ \"command\": [\"script-message\", \"add_to_queue\", \"$URL\" ] }" | socat - "$MPV_SOCKET" &> /dev/null; then - notify-send -i "$ICON_PATH" "$TITLE" "Added $URL to queue" - else - notify-send -i "$ICON_PATH" "$TITLE" "Failed to add $URL to queue" + if [[ -S "$MPV_SOCKET" ]] && wait_for_socket && echo "{ \"command\": [\"script-message\", \"add_to_queue\", \"$URL\" ] }" | socat - "$MPV_SOCKET" &> /dev/null; then + notify "Added $URL to queue" + exit 0 fi + + notify "Queue unavailable, opening in new player" + play_direct + notify "Playing $URL" fi diff --git a/projects/scripts/rmpv b/projects/scripts/rmpv index dddc8be..d91bd0f 100755 --- a/projects/scripts/rmpv +++ b/projects/scripts/rmpv @@ -4,6 +4,7 @@ THEME="${THEME:-$HOME/.local/share/SubMiner/themes/subminer.rasi}" FONTCONFIG_FILE=$HOME/.config/mpv/mpv-fonts.conf COMMAND=mpv VIDEO_EXTENSIONS="mkv|mp4|avi|webm|mov|flv|wmv|m4v|ts|m2ts" +TARGET_DIRECTORY="" # Parse command-line options first while getopts ":st:" opt; do @@ -26,6 +27,19 @@ while getopts ":st:" opt; do done shift $((OPTIND - 1)) +if [[ $# -gt 1 ]]; then + echo "Usage: $0 [-s] [-t theme] [directory]" >&2 + exit 1 +fi + +if [[ $# -eq 1 ]]; then + if [[ ! -d "$1" ]]; then + echo "Invalid directory: $1" >&2 + exit 1 + fi + TARGET_DIRECTORY="$(realpath "$1")" +fi + find_videos() { find "$PWD" -maxdepth 1 -type f -regextype posix-extended \ -iregex ".*\.($VIDEO_EXTENSIONS)$" 2> /dev/null | sort -V @@ -40,6 +54,60 @@ build_rofi_menu() { done < <(find_videos) } +is_video_file() { + local file="$1" + local regex="\\.($VIDEO_EXTENSIONS)$" + + if [[ ! "${file,,}" =~ $regex ]]; then + return 1 + fi + + if command -v file > /dev/null 2>&1; then + local mime + mime=$(file -b --mime-type "$file" 2>/dev/null || true) + [[ "$mime" == video/* ]] && return 0 + fi + + return 0 +} + +select_video_via_rofi() { + if [[ -n "$TARGET_DIRECTORY" ]]; then + local selection="" + local prompt="Choose Video " + while true; do + selection=$(rofi -show filebrowser -show-icons -theme "$THEME" \ + -theme-str 'configuration {font: "JetBrainsMono Nerd Font 10";} listview {columns: 1; lines: 15;} window {width: 88%;}' \ + -p "$prompt" \ + -filebrowser-directory "$TARGET_DIRECTORY" \ + -filebrowser-cancel-returns-1 true) + + [[ -z "$selection" ]] && return 1 + + if [[ "$selection" != /* ]]; then + selection="$TARGET_DIRECTORY/$selection" + fi + + if [[ -d "$selection" ]]; then + prompt="Choose Video (folders are not selectable)" + continue + fi + + if is_video_file "$selection"; then + echo "$selection" + return 0 + fi + + prompt="Choose Video (select a video file)" + done + fi + + build_rofi_menu | rofi -dmenu -i -show-icons -theme "$THEME" \ + -theme-str 'configuration {font: "JetBrainsMono Nerd Font 10";} listview {columns: 1; lines: 15;} window {width: 88%;}' -p "Choose Video " +} + +selection=$(select_video_via_rofi) + get_video_thumbnail() { local video="$1" local thumb_dir="$HOME/.cache/thumbnails/large" @@ -60,15 +128,24 @@ get_video_thumbnail() { fi } -selection=$(build_rofi_menu | rofi -dmenu -i -show-icons -theme "$THEME" \ - -theme-str 'configuration {font: "JetBrainsMono Nerd Font 10";} listview {columns: 1; lines: 15;} window {width: 88%;}' -p "Choose Video ") - if [[ -z "$selection" ]]; then echo "No video selected." exit 1 fi -choice="./$selection" +if [[ -n "$TARGET_DIRECTORY" ]]; then + choice="$selection" + if [[ "$choice" != /* ]]; then + choice="$TARGET_DIRECTORY/$choice" + fi +else + choice="./$selection" +fi + +if [[ ! -f "$choice" ]]; then + echo "Selected item is not a valid file: $choice" >&2 + exit 1 +fi THUMBNAIL_PATH=$(get_video_thumbnail "$choice") if [[ -n "$THUMBNAIL_PATH" && -f "$THUMBNAIL_PATH" ]]; then