mirror of
https://github.com/zhenyan121/dotfiles.git
synced 2026-04-10 14:34:09 +08:00
feat: 上传我的配置
This commit is contained in:
134
.config/waybar/scripts/longshot-sh/longshot-grim.sh
Executable file
134
.config/waybar/scripts/longshot-sh/longshot-grim.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
|
||||
# =================语言=================
|
||||
if env | grep -q "zh_CN"; then
|
||||
STR_NEXT="📸 截取下一张 (仅需定高度)"
|
||||
STR_FINISH="💾 完成并处理"
|
||||
STR_ABORT="❌ 放弃"
|
||||
STR_ERR="错误"
|
||||
STR_SAVED="已保存"
|
||||
else
|
||||
STR_NEXT="📸 Capture Next (Height only)"
|
||||
STR_FINISH="💾 Finish"
|
||||
STR_ABORT="❌ Abort"
|
||||
STR_ERR="Error"
|
||||
STR_SAVED="Saved"
|
||||
fi
|
||||
|
||||
# =================配置=================
|
||||
CONFIG_DIR="$HOME/.cache/longshot-sh"
|
||||
CONFIG_FILE="$CONFIG_DIR/mode"
|
||||
SAVE_DIR="$HOME/Pictures/Screenshots/longshots"
|
||||
|
||||
TMP_DIR="/tmp/longshot_grim_$(date +%s)"
|
||||
FILENAME="longshot_$(date +%Y%m%d_%H%M%S).png"
|
||||
RESULT_PATH="$SAVE_DIR/$FILENAME"
|
||||
TMP_STITCHED="$TMP_DIR/stitched.png"
|
||||
|
||||
mkdir -p "$SAVE_DIR" "$TMP_DIR"
|
||||
cleanup() { rm -rf "$TMP_DIR"; }
|
||||
trap cleanup EXIT SIGINT SIGTERM
|
||||
|
||||
# 菜单工具
|
||||
# [修改]: 将 fuzzel 宽度从 45 调整为 30,使其更紧凑
|
||||
CMD_FUZZEL="fuzzel -d --anchor=top --y-margin=20 --lines=3 --width=30"
|
||||
CMD_WOFI="wofi --dmenu --lines 3"
|
||||
CMD_ROFI="rofi -dmenu -l 3"
|
||||
|
||||
if command -v fuzzel &> /dev/null; then MENU_CMD="$CMD_FUZZEL"
|
||||
elif command -v wofi &> /dev/null; then MENU_CMD="$CMD_WOFI"
|
||||
elif command -v rofi &> /dev/null; then MENU_CMD="$CMD_ROFI"
|
||||
else exit 1; fi
|
||||
|
||||
# [新增]: 动态计算宽度函数 (主要针对 wofi)
|
||||
function get_dynamic_width() {
|
||||
local text="$1"
|
||||
# 获取最长行的长度
|
||||
local max_len=$(echo -e "$text" | wc -L)
|
||||
# 计算: 字符数 * 28px + 60px 边距 (可根据屏幕分辨率微调)
|
||||
echo $(( max_len * 28 + 60 ))
|
||||
}
|
||||
|
||||
# [修改]: 增加对 wofi 的动态宽度支持
|
||||
function show_menu() {
|
||||
local content="$1"
|
||||
|
||||
if [[ "$MENU_CMD" == *"wofi"* ]]; then
|
||||
# 如果是 wofi,计算宽度并附加参数
|
||||
local width=$(get_dynamic_width "$content")
|
||||
echo -e "$content" | $MENU_CMD --width "$width"
|
||||
else
|
||||
# 其他工具 (fuzzel/rofi) 保持原样
|
||||
echo -e "$content" | $MENU_CMD
|
||||
fi
|
||||
}
|
||||
|
||||
# ======================================
|
||||
# Step 1: 第一张截图 (直接开始,不询问)
|
||||
# ======================================
|
||||
# 用户在主菜单点击了 "选择区域",所以这里直接 Slurp
|
||||
GEO_1=$(slurp)
|
||||
if [ -z "$GEO_1" ]; then exit 0; fi
|
||||
|
||||
IFS=', x' read -r FIX_X FIX_Y FIX_W FIX_H <<< "$GEO_1"
|
||||
grim -g "$GEO_1" "$TMP_DIR/001.png"
|
||||
|
||||
# ======================================
|
||||
# Step 2: 循环截图
|
||||
# ======================================
|
||||
INDEX=2
|
||||
DO_SAVE=false
|
||||
|
||||
while true; do
|
||||
# 菜单提示下一张 (show_menu 会自动处理宽度)
|
||||
ACTION=$(show_menu "$STR_NEXT\n$STR_FINISH\n$STR_ABORT")
|
||||
|
||||
case "$ACTION" in
|
||||
*"📸"*)
|
||||
sleep 0.2
|
||||
GEO_NEXT=$(slurp)
|
||||
if [ -z "$GEO_NEXT" ]; then continue; fi
|
||||
|
||||
# 锁定宽度和X轴,只取新高度
|
||||
IFS=', x' read -r _TX NEW_Y _TW NEW_H <<< "$GEO_NEXT"
|
||||
FINAL_GEO="${FIX_X},${NEW_Y} ${FIX_W}x${NEW_H}"
|
||||
|
||||
IMG_NAME="$(printf "%03d" $INDEX).png"
|
||||
grim -g "$FINAL_GEO" "$TMP_DIR/$IMG_NAME"
|
||||
((INDEX++))
|
||||
;;
|
||||
*"💾"*) DO_SAVE=true; break ;;
|
||||
*"❌"*) exit 0 ;;
|
||||
*) break ;; # 意外退出
|
||||
esac
|
||||
done
|
||||
|
||||
# ======================================
|
||||
# Step 3: 处理与自动动作
|
||||
# ======================================
|
||||
COUNT=$(ls "$TMP_DIR"/*.png 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$COUNT" -gt 0 ] && [ "$DO_SAVE" = true ]; then
|
||||
# 拼接
|
||||
magick "$TMP_DIR"/*.png -append "$TMP_STITCHED"
|
||||
mv "$TMP_STITCHED" "$RESULT_PATH"
|
||||
|
||||
# 复制到剪贴板
|
||||
if command -v wl-copy &> /dev/null; then wl-copy < "$RESULT_PATH"; fi
|
||||
|
||||
# 读取配置执行动作
|
||||
FINAL_MODE=$(cat "$CONFIG_FILE" 2>/dev/null || echo "PREVIEW")
|
||||
|
||||
case "$FINAL_MODE" in
|
||||
"PREVIEW")
|
||||
imv "$RESULT_PATH"
|
||||
;;
|
||||
"EDIT")
|
||||
if command -v satty &> /dev/null; then satty -f "$RESULT_PATH"
|
||||
else imv "$RESULT_PATH"; fi
|
||||
;;
|
||||
"SAVE")
|
||||
notify-send -i "$RESULT_PATH" "Longshot" "$STR_SAVED: $(basename "$RESULT_PATH")"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
80
.config/waybar/scripts/longshot-sh/longshot-wf-recorder.sh
Executable file
80
.config/waybar/scripts/longshot-sh/longshot-wf-recorder.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_PYTHON="$SCRIPT_DIR/venv/bin/python"
|
||||
PY_STITCH="$SCRIPT_DIR/stitch.py"
|
||||
|
||||
CONFIG_DIR="$HOME/.cache/longshot-sh"
|
||||
CONFIG_FILE="$CONFIG_DIR/mode"
|
||||
SAVE_DIR="$HOME/Pictures/Screenshots/longshots"
|
||||
mkdir -p "$SAVE_DIR"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
TMP_VIDEO="/tmp/longshot_${TIMESTAMP}.mp4"
|
||||
OUTPUT_IMG="${SAVE_DIR}/longshot_${TIMESTAMP}.png"
|
||||
|
||||
# 语言
|
||||
if env | grep -q "zh_CN"; then
|
||||
TXT_REC="录制中"
|
||||
TXT_MSG="正在拼接..."
|
||||
TXT_SAVED="已保存并复制"
|
||||
WIDTH=6
|
||||
else
|
||||
TXT_REC="Recording"
|
||||
TXT_MSG="Stitching..."
|
||||
TXT_SAVED="Saved"
|
||||
WIDTH=10
|
||||
fi
|
||||
|
||||
# 录制菜单
|
||||
if command -v fuzzel &> /dev/null; then
|
||||
MENU_REC_CMD="fuzzel -d --anchor top --y-margin 20 --width $WIDTH --lines 0"
|
||||
elif command -v wofi &> /dev/null; then
|
||||
MENU_REC_CMD="wofi -d -i -p Rec"
|
||||
else
|
||||
MENU_REC_CMD="rofi -dmenu -p Rec"
|
||||
fi
|
||||
|
||||
# Step 1: 选区
|
||||
GEOMETRY=$(slurp)
|
||||
if [ -z "$GEOMETRY" ]; then exit 1; fi
|
||||
|
||||
# Step 2: 录制
|
||||
wf-recorder -g "$GEOMETRY" -f "$TMP_VIDEO" \
|
||||
-c libx264 -p crf=0 -p preset=ultrafast -p pixel_format=yuv420p \
|
||||
&> /dev/null &
|
||||
REC_PID=$!
|
||||
|
||||
sleep 0.5
|
||||
if ! kill -0 $REC_PID 2>/dev/null; then
|
||||
notify-send "Error" "wf-recorder failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: 停止菜单
|
||||
echo "Stop" | $MENU_REC_CMD -p "$TXT_REC" > /dev/null
|
||||
|
||||
kill -SIGINT $REC_PID
|
||||
wait $REC_PID 2>/dev/null
|
||||
|
||||
# Step 4: 处理
|
||||
if [ -f "$TMP_VIDEO" ]; then
|
||||
notify-send -t 2000 "Longshot" "$TXT_MSG"
|
||||
|
||||
"$VENV_PYTHON" "$PY_STITCH" "$TMP_VIDEO" "$OUTPUT_IMG"
|
||||
rm -f "$TMP_VIDEO"
|
||||
|
||||
if [ -f "$OUTPUT_IMG" ]; then
|
||||
if command -v wl-copy &> /dev/null; then wl-copy < "$OUTPUT_IMG"; fi
|
||||
|
||||
# 自动执行动作
|
||||
FINAL_MODE=$(cat "$CONFIG_FILE" 2>/dev/null || echo "PREVIEW")
|
||||
case "$FINAL_MODE" in
|
||||
"PREVIEW") imv "$OUTPUT_IMG" ;;
|
||||
"EDIT") if command -v satty &> /dev/null; then satty -f "$OUTPUT_IMG"; else imv "$OUTPUT_IMG"; fi ;;
|
||||
"SAVE") notify-send "Longshot" "$TXT_SAVED: $(basename "$OUTPUT_IMG")" ;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
notify-send "Error" "No video"
|
||||
fi
|
||||
224
.config/waybar/scripts/longshot-sh/longshot.sh
Executable file
224
.config/waybar/scripts/longshot-sh/longshot.sh
Executable file
@@ -0,0 +1,224 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_PYTHON="$SCRIPT_DIR/venv/bin/python"
|
||||
|
||||
# =================配置区=================
|
||||
CONFIG_DIR="$HOME/.cache/longshot-sh"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
FILE_MODE="$CONFIG_DIR/mode" # PREVIEW / EDIT / SAVE
|
||||
FILE_BACKEND="$CONFIG_DIR/backend" # WF / GRIM
|
||||
|
||||
# 初始化默认值
|
||||
[ ! -f "$FILE_MODE" ] && echo "PREVIEW" > "$FILE_MODE"
|
||||
[ ! -f "$FILE_BACKEND" ] && echo "WF" > "$FILE_BACKEND"
|
||||
|
||||
# =================语言资源=================
|
||||
if env | grep -q "zh_CN"; then
|
||||
TXT_TITLE_WF="缓慢滚动,回车停止"
|
||||
TXT_TITLE_GRIM="记住截图末尾位置"
|
||||
|
||||
TXT_START="📷 选择截图区域"
|
||||
TXT_SETTING="⚙️ 设置"
|
||||
TXT_EXIT="❌ 退出"
|
||||
|
||||
TXT_BACK="🔙 返回主菜单"
|
||||
TXT_SW_BACKEND="📹 切换后端"
|
||||
TXT_SW_ACTION="🛠 切换动作"
|
||||
TXT_PROMPT_ACTION="请选择截图后的动作:"
|
||||
|
||||
TXT_ST_WF="流式录制 (wf-recorder)"
|
||||
TXT_ST_GRIM="分段截图 (grim)"
|
||||
|
||||
TXT_ST_PRE="预览 (imv)"
|
||||
TXT_ST_EDIT="编辑 (satty)"
|
||||
TXT_ST_SAVE="仅保存"
|
||||
|
||||
# 初始化提示
|
||||
TXT_MSG_INIT="首次运行,正在初始化环境..."
|
||||
TXT_MSG_SETUP_DONE="环境初始化完成!"
|
||||
TXT_ERR_SETUP="环境安装失败,请查看 /tmp/longshot_setup.log"
|
||||
TXT_ERR_NO_SETUP="未找到 setup.sh 文件"
|
||||
|
||||
# 新增:依赖缺失提示
|
||||
TXT_ERR_DEP_TITLE="缺少系统依赖"
|
||||
TXT_ERR_DEP_MSG="请安装以下包:"
|
||||
else
|
||||
TXT_TITLE_WF="Scroll Slowly, Enter to Stop"
|
||||
TXT_TITLE_GRIM="Remember End Position"
|
||||
|
||||
TXT_START="📷 Select Area"
|
||||
TXT_SETTING="⚙️ Settings"
|
||||
TXT_EXIT="❌ Exit"
|
||||
|
||||
TXT_BACK="🔙 Back"
|
||||
TXT_SW_BACKEND="📹 Switch Backend"
|
||||
TXT_SW_ACTION="🛠 Switch Action"
|
||||
TXT_PROMPT_ACTION="Select action after capture:"
|
||||
|
||||
TXT_ST_WF="Stream (wf-recorder)"
|
||||
TXT_ST_GRIM="Manual (grim)"
|
||||
|
||||
TXT_ST_PRE="Preview"
|
||||
TXT_ST_EDIT="Edit"
|
||||
TXT_ST_SAVE="Save Only"
|
||||
|
||||
# Init messages
|
||||
TXT_MSG_INIT="First run, initializing environment..."
|
||||
TXT_MSG_SETUP_DONE="Environment initialized!"
|
||||
TXT_ERR_SETUP="Setup failed, check /tmp/longshot_setup.log"
|
||||
TXT_ERR_NO_SETUP="setup.sh not found"
|
||||
|
||||
# New: Dependency error
|
||||
TXT_ERR_DEP_TITLE="Missing Dependencies"
|
||||
TXT_ERR_DEP_MSG="Please install:"
|
||||
fi
|
||||
|
||||
# ================= 1. 系统依赖检测 (新增) =================
|
||||
# 检测核心工具: wf-recorder, grim, slurp, imagemagick (magick)
|
||||
REQUIRED_TOOLS=("wf-recorder" "grim" "slurp" "magick" "wl-copy")
|
||||
MISSING_TOOLS=()
|
||||
|
||||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
MISSING_TOOLS+=("$tool")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then
|
||||
# 构建错误信息
|
||||
MSG="${TXT_ERR_DEP_MSG} ${MISSING_TOOLS[*]}"
|
||||
|
||||
# 尝试发送通知
|
||||
if command -v notify-send &> /dev/null; then
|
||||
notify-send -u critical "$TXT_ERR_DEP_TITLE" "$MSG"
|
||||
else
|
||||
# 如果连 notify-send 都没有,这就很尴尬了,尝试用 echo 输出到 stderr
|
||||
echo "❌ $TXT_ERR_DEP_TITLE: $MSG" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ================= 2. Python 环境自动检测与修复 =================
|
||||
if [ ! -f "$VENV_PYTHON" ]; then
|
||||
notify-send -t 5000 "Longshot" "$TXT_MSG_INIT"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/setup.sh" ]; then
|
||||
chmod +x "$SCRIPT_DIR/setup.sh"
|
||||
"$SCRIPT_DIR/setup.sh" > /tmp/longshot_setup.log 2>&1
|
||||
|
||||
if [ ! -f "$VENV_PYTHON" ]; then
|
||||
notify-send -u critical "Error" "$TXT_ERR_SETUP"
|
||||
exit 1
|
||||
else
|
||||
notify-send -t 3000 "Longshot" "$TXT_MSG_SETUP_DONE"
|
||||
fi
|
||||
else
|
||||
notify-send -u critical "Error" "$TXT_ERR_NO_SETUP"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# =================菜单工具=================
|
||||
if command -v fuzzel &> /dev/null; then
|
||||
MENU_CMD="fuzzel -d --anchor top --y-margin 20 --width 35 --lines 4"
|
||||
elif command -v wofi &> /dev/null; then
|
||||
MENU_CMD="wofi -d -i -p Longshot"
|
||||
else
|
||||
MENU_CMD="rofi -dmenu"
|
||||
fi
|
||||
|
||||
# =================主循环=================
|
||||
while true; do
|
||||
# 1. 读取当前配置
|
||||
CUR_MODE=$(cat "$FILE_MODE")
|
||||
CUR_BACKEND=$(cat "$FILE_BACKEND")
|
||||
|
||||
# 2. 动态生成 UI 文本
|
||||
CURRENT_TITLE=""
|
||||
if [ "$CUR_BACKEND" == "WF" ]; then
|
||||
CURRENT_TITLE="$TXT_TITLE_WF"
|
||||
else
|
||||
CURRENT_TITLE="$TXT_TITLE_GRIM"
|
||||
fi
|
||||
|
||||
LBL_MODE=""
|
||||
case "$CUR_MODE" in
|
||||
"PREVIEW") LBL_MODE="$TXT_ST_PRE" ;;
|
||||
"EDIT") LBL_MODE="$TXT_ST_EDIT" ;;
|
||||
"SAVE") LBL_MODE="$TXT_ST_SAVE" ;;
|
||||
esac
|
||||
|
||||
LBL_BACKEND=""
|
||||
case "$CUR_BACKEND" in
|
||||
"WF") LBL_BACKEND="$TXT_ST_WF" ;;
|
||||
"GRIM") LBL_BACKEND="$TXT_ST_GRIM" ;;
|
||||
esac
|
||||
|
||||
# 3. 显示主菜单
|
||||
OPTION_START="$TXT_START"
|
||||
OPTION_SETTING="$TXT_SETTING [$LBL_BACKEND | $LBL_MODE]"
|
||||
OPTION_EXIT="$TXT_EXIT"
|
||||
|
||||
if [[ "$MENU_CMD" == *"fuzzel"* ]] || [[ "$MENU_CMD" == *"rofi"* ]]; then
|
||||
CHOICE=$(echo -e "$OPTION_START\n$OPTION_SETTING\n$OPTION_EXIT" | $MENU_CMD -p "$CURRENT_TITLE")
|
||||
else
|
||||
CHOICE=$(echo -e "$OPTION_START\n$OPTION_SETTING\n$OPTION_EXIT" | $MENU_CMD)
|
||||
fi
|
||||
|
||||
# 4. 处理选择
|
||||
if [[ "$CHOICE" == *"$TXT_START"* ]]; then
|
||||
# === 启动后端 ===
|
||||
if [ "$CUR_BACKEND" == "WF" ]; then
|
||||
exec "$SCRIPT_DIR/longshot-wf-recorder.sh"
|
||||
else
|
||||
exec "$SCRIPT_DIR/longshot-grim.sh"
|
||||
fi
|
||||
break
|
||||
|
||||
elif [[ "$CHOICE" == *"$TXT_SETTING"* ]]; then
|
||||
# === 设置菜单循环 ===
|
||||
while true; do
|
||||
S_MODE=$(cat "$FILE_MODE")
|
||||
S_BACK=$(cat "$FILE_BACKEND")
|
||||
|
||||
D_BACK=""; [ "$S_BACK" == "WF" ] && D_BACK="$TXT_ST_WF" || D_BACK="$TXT_ST_GRIM"
|
||||
D_MODE="";
|
||||
case "$S_MODE" in
|
||||
"PREVIEW") D_MODE="$TXT_ST_PRE" ;;
|
||||
"EDIT") D_MODE="$TXT_ST_EDIT" ;;
|
||||
"SAVE") D_MODE="$TXT_ST_SAVE" ;;
|
||||
esac
|
||||
|
||||
ITEM_BACKEND="$TXT_SW_BACKEND [$D_BACK]"
|
||||
ITEM_ACTION="$TXT_SW_ACTION [$D_MODE]"
|
||||
|
||||
if [[ "$MENU_CMD" == *"fuzzel"* ]] || [[ "$MENU_CMD" == *"rofi"* ]]; then
|
||||
S_CHOICE=$(echo -e "$TXT_BACK\n$ITEM_BACKEND\n$ITEM_ACTION" | $MENU_CMD -p "$TXT_SETTING")
|
||||
else
|
||||
S_CHOICE=$(echo -e "$TXT_BACK\n$ITEM_BACKEND\n$ITEM_ACTION" | $MENU_CMD)
|
||||
fi
|
||||
|
||||
if [[ "$S_CHOICE" == *"$TXT_BACK"* ]]; then
|
||||
break
|
||||
elif [[ "$S_CHOICE" == *"$TXT_SW_BACKEND"* ]]; then
|
||||
if [ "$S_BACK" == "WF" ]; then echo "GRIM" > "$FILE_BACKEND"; else echo "WF" > "$FILE_BACKEND"; fi
|
||||
elif [[ "$S_CHOICE" == *"$TXT_SW_ACTION"* ]]; then
|
||||
if [[ "$MENU_CMD" == *"fuzzel"* ]] || [[ "$MENU_CMD" == *"rofi"* ]]; then
|
||||
A_CHOICE=$(echo -e "$TXT_ST_PRE\n$TXT_ST_EDIT\n$TXT_ST_SAVE" | $MENU_CMD -p "$TXT_PROMPT_ACTION")
|
||||
else
|
||||
A_CHOICE=$(echo -e "$TXT_ST_PRE\n$TXT_ST_EDIT\n$TXT_ST_SAVE" | $MENU_CMD)
|
||||
fi
|
||||
|
||||
if [[ "$A_CHOICE" == *"$TXT_ST_PRE"* ]]; then echo "PREVIEW" > "$FILE_MODE"; fi
|
||||
if [[ "$A_CHOICE" == *"$TXT_ST_EDIT"* ]]; then echo "EDIT" > "$FILE_MODE"; fi
|
||||
if [[ "$A_CHOICE" == *"$TXT_ST_SAVE"* ]]; then echo "SAVE" > "$FILE_MODE"; fi
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
42
.config/waybar/scripts/longshot-sh/setup.sh
Executable file
42
.config/waybar/scripts/longshot-sh/setup.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
echo "🔧 初始化环境..."
|
||||
|
||||
# 1. 检查系统工具
|
||||
# 涵盖了两个方案所需的所有工具
|
||||
REQUIRED_TOOLS=("wf-recorder" "grim" "slurp" "wl-copy" "magick" "python3")
|
||||
MISSING_TOOLS=()
|
||||
|
||||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
MISSING_TOOLS+=("$tool")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then
|
||||
echo "⚠️ 警告: 缺少以下系统工具,请使用包管理器安装:"
|
||||
echo " ${MISSING_TOOLS[*]}"
|
||||
echo " (例如 Arch: sudo pacman -S wf-recorder grim slurp wl-clipboard imagemagick python)"
|
||||
echo " (例如 Debian/Ubuntu: sudo apt install wf-recorder grim slurp wl-clipboard imagemagick python3-venv)"
|
||||
fi
|
||||
|
||||
# 2. Python 虚拟环境
|
||||
if [ ! -d "$VENV_DIR" ]; then
|
||||
echo "📦 创建 Python 虚拟环境..."
|
||||
python3 -m venv "$VENV_DIR"
|
||||
fi
|
||||
|
||||
# 3. 安装 Python 依赖
|
||||
echo "⬇️ 安装 Python 库..."
|
||||
"$VENV_DIR/bin/pip" install --upgrade pip > /dev/null
|
||||
"$VENV_DIR/bin/pip" install opencv-python numpy > /dev/null
|
||||
|
||||
# 4. 赋予执行权限
|
||||
chmod +x "$SCRIPT_DIR/longshot.sh"
|
||||
chmod +x "$SCRIPT_DIR/longshot-wf-recorder.sh"
|
||||
chmod +x "$SCRIPT_DIR/longshot-grim.sh"
|
||||
chmod +x "$SCRIPT_DIR/stitch.py"
|
||||
|
||||
echo "✅ 完成!请运行 ./longshot.sh"
|
||||
105
.config/waybar/scripts/longshot-sh/stitch.py
Executable file
105
.config/waybar/scripts/longshot-sh/stitch.py
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
import cv2
|
||||
import numpy as np
|
||||
import sys
|
||||
import os
|
||||
|
||||
def stitch_video(video_path, output_path):
|
||||
if not os.path.exists(video_path):
|
||||
return
|
||||
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
if not cap.isOpened():
|
||||
print("❌ 无法打开视频")
|
||||
return
|
||||
|
||||
frames = []
|
||||
ret, prev_frame = cap.read()
|
||||
if not ret: return
|
||||
|
||||
frames.append(prev_frame)
|
||||
anchor_frame = prev_frame.copy()
|
||||
|
||||
# ==========================
|
||||
# 核心参数 (手动滚动优化)
|
||||
# ==========================
|
||||
MIN_SCROLL = 2
|
||||
MATCH_CONFIDENCE = 0.5
|
||||
|
||||
# 忽略上下边缘 (防止浏览器地址栏/状态栏干扰)
|
||||
IGNORE_Y_TOP = 0.15
|
||||
IGNORE_Y_BOTTOM = 0.15
|
||||
IGNORE_X = 0.15
|
||||
|
||||
h, w, _ = anchor_frame.shape
|
||||
|
||||
# 有效特征区
|
||||
x1 = int(w * IGNORE_X)
|
||||
x2 = int(w * (1 - IGNORE_X))
|
||||
y1 = int(h * IGNORE_Y_TOP)
|
||||
template_h = int(h * 0.2)
|
||||
|
||||
print(f"⚡ 正在分析 (梯度匹配模式)...")
|
||||
|
||||
last_shift = 0
|
||||
SEARCH_WINDOW = 50
|
||||
|
||||
while True:
|
||||
ret, curr_frame = cap.read()
|
||||
if not ret: break
|
||||
|
||||
# 1. 梯度处理 (解决白底黑字问题)
|
||||
curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)
|
||||
anchor_gray = cv2.cvtColor(anchor_frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
curr_grad = cv2.Sobel(curr_gray, cv2.CV_8U, 0, 1, ksize=3)
|
||||
anchor_grad = cv2.Sobel(anchor_gray, cv2.CV_8U, 0, 1, ksize=3)
|
||||
|
||||
# 2. 提取模板
|
||||
template = curr_grad[y1 : y1 + template_h, x1:x2]
|
||||
roi = anchor_grad[y1:, x1:x2]
|
||||
|
||||
# 3. 匹配
|
||||
res = cv2.matchTemplate(roi, template, cv2.TM_CCOEFF_NORMED)
|
||||
|
||||
# 4. 惯性约束
|
||||
if last_shift > 0:
|
||||
mask = np.zeros_like(res)
|
||||
target_y = last_shift
|
||||
y_min = max(0, target_y - SEARCH_WINDOW)
|
||||
y_max = min(res.shape[0], target_y + SEARCH_WINDOW)
|
||||
mask[y_min:y_max, :] = 1
|
||||
res = np.multiply(res, mask)
|
||||
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
|
||||
shift = max_loc[1]
|
||||
|
||||
# 5. 判定
|
||||
if max_val > MATCH_CONFIDENCE and shift > MIN_SCROLL and shift < (roi.shape[0] - 5):
|
||||
new_content_start_y = h - shift
|
||||
if new_content_start_y < h:
|
||||
new_part = curr_frame[new_content_start_y:, :, :]
|
||||
frames.append(new_part)
|
||||
anchor_frame = curr_frame.copy()
|
||||
|
||||
if last_shift == 0: last_shift = shift
|
||||
else: last_shift = int(last_shift * 0.6 + shift * 0.4)
|
||||
|
||||
cap.release()
|
||||
|
||||
if len(frames) > 1:
|
||||
try:
|
||||
full_image = np.vstack(frames)
|
||||
cv2.imwrite(output_path, full_image, [cv2.IMWRITE_PNG_COMPRESSION, 3])
|
||||
print(f"🎉 处理完成")
|
||||
except Exception as e:
|
||||
print(f"❌ 保存失败: {e}")
|
||||
else:
|
||||
print("⚠️ 未检测到滚动,保存第一帧")
|
||||
cv2.imwrite(output_path, frames[0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python stitch.py <input_video> <output_image>")
|
||||
else:
|
||||
stitch_video(sys.argv[1], sys.argv[2])
|
||||
Reference in New Issue
Block a user