KeyMouseGo使用心得

notepad--优化代码

  • 坐标点位正则表达式替换鼠标抬起坐标,由坐标点位-->当前点击位置
    left up",action: \[.*?\]-->left up",action: \[-1\, -1\]
    image

JSON5脚本批量处理工具 全量最终需求文档

优先级说明:核心约束 > 功能规则 > 输出规则


一、核心约束(最高优先级,所有实现必须严格遵守)


  1. 处理全程不得破坏原文件的原始格式与内容结构,禁止通过JSON/JSON5解析后重写的方式实现,必须采用纯文本逐行处理方案
  2. 必须100%完整保留原文件所有内容:包含//开头的注释行、空行、外层结构的缩进与换行,输出文件与原文件视觉风格完全一致
  3. 仅可对符合规则的内容进行修改,其余内容、格式、标点不得改动,允许的修改范围仅为:
    3.1 删除符合规则的mouse move事件行
    3.2 修改指定事件的delay数值
    3.3 去除指定位置之间的空白字符
  4. 所有处理均不修改、不覆盖原文件,处理结果生成独立新文件,新文件默认保存在原文件同级目录

二、基础交互要求


  1. 支持批量处理 .json5 格式文件
  2. 通过系统原生可视化文件选择窗口选文件,支持按住Ctrl多选多个文件
  3. 处理完成后在控制台输出处理统计:成功数量、失败数量、对应文件路径

三、功能1:JSON5格式单行化处理


3.1 需去除空白、合并为一行的规则
对以下位置之间的所有空白字符(空格、回车、换行、制表符等全部空白)进行去除,合并为同一行:

  1. { 与 delay 之间
  2. , 与 event_type 之间
  3. , 与 action_type 之间
  4. , 与 action 之间
  5. [ 与 " 之间
  6. , 与 " 之间
  7. , 与 type 之间
  8. 字符串数组(如action字段的数组)结尾的 , 与 ] 之间

3.2 禁止合并规则(必须保留原有换行与缩进)
以下场景不得去除空白、不得合并,必须保留原始格式:

  1. 最外层scripts对象数组结尾的 }, 与 ] 之间
  2. 最外层结构的 ], 与 } 之间
  3. 所有注释行、空行、外层声明行(如 scripts: [ )

四、功能2:鼠标事件delay累加与精简处理

处理对象:文件中scripts数组内的所有事件


4.1 普通状态基础规则

  1. 遍历事件序列,遇到 action_type 为 "mouse move" 的事件时,将该事件的delay数值累加到延迟累计池,同时删除该事件行
  2. 遇到第一个非 "mouse move" 类型的事件时,将延迟累计池的总数值叠加到该事件的delay字段上,叠加完成后重置累计池
  3. 若文件末尾全部为mouse move事件、无后续非move事件,这些事件直接删除,累计的延迟值不再处理

4.2 拖拽场景特殊保护规则

  1. 触发条件:遇到 action_type 为 "mouse left down" 的事件后,立即进入拖拽保护状态
  2. 保护期规则:拖拽状态内,所有mouse move事件完整保留,不参与延迟累加、不被删除
  3. 退出条件:遇到 action_type 为 "mouse left up" 的事件后,立即退出拖拽保护状态,恢复普通状态规则
  4. 边界兼容:若只有mouse left down事件、无对应mouse left up事件,后续所有mouse move事件持续保留

五、输出文件命名规则


根据执行的功能组合,新文件命名规则如下:

  1. 仅执行格式单行化处理:原文件名 + Format + 原后缀 (例:record.json5 → recordFormat.json5)
  2. 仅执行事件精简处理:原文件名 + _Delay + 原后缀 (例:record.json5 → record_Delay.json5)
  3. 同时执行格式单行化+事件精简处理:原文件名 + _FormatDelay + 原后缀 (例:record.json5 → record_FormatDelay.json5)

脚本批量处理录屏文件JSON5

点击查看代码
import tkinter as tk
from tkinter import filedialog
import os
import re

# ===================== 功能配置开关 =====================
# 开启=执行对应功能,自动匹配对应输出文件名规则
ENABLE_FORMAT_PROCESS = True   # 是否执行【格式单行化处理】
ENABLE_DELAY_PROCESS = True    # 是否执行【鼠标事件delay累加精简处理】
# =======================================================


def process_delay_events(lines):
    """
    功能2:鼠标事件delay累加与精简处理
    严格遵守拖拽保护规则,所有非事件行(注释、空行、外层结构)原样保留
    依赖:输入的每个事件必须为完整单行(先经过格式单行化处理)
    """
    result_lines = []
    delay_accumulator = 0  # 普通状态下的delay累计池
    is_dragging = False    # 拖拽保护状态标记

    for line in lines:
        stripped_line = line.strip()

        # 非事件行:注释、空行、外层结构行,直接原样保留
        if (stripped_line.startswith('//') 
            or stripped_line == '' 
            or ('action_type' not in line and 'delay' not in line)):
            result_lines.append(line)
            continue

        # 提取事件核心字段,提取失败则原样保留
        action_match = re.search(r'action_type:\s*"([^"]+)"', line)
        delay_match = re.search(r'delay:\s*(\d+)', line)
        if not action_match or not delay_match:
            result_lines.append(line)
            continue

        action_type = action_match.group(1)
        origin_delay = int(delay_match.group(1))

        # ========== 拖拽状态:所有事件完整保留 ==========
        if is_dragging:
            result_lines.append(line)
            # 遇到鼠标抬起事件,退出拖拽保护状态
            if action_type == 'mouse left up':
                is_dragging = False
            continue

        # ========== 普通状态:累加+删除规则 ==========
        if action_type == 'mouse move':
            # 累加delay,不保留该事件行
            delay_accumulator += origin_delay
        else:
            # 将累计delay叠加到当前非move事件
            new_delay = origin_delay + delay_accumulator
            new_line = re.sub(r'delay:\s*\d+', f'delay: {new_delay}', line, count=1)
            result_lines.append(new_line)
            delay_accumulator = 0

            # 遇到鼠标按下事件,进入拖拽保护状态
            if action_type == 'mouse left down':
                is_dragging = True

    return result_lines


def process_format(content):
    """
    功能1:JSON5格式单行化处理
    仅去除指定位置空白,严格遵守最外层结构禁止合并规则
    """
    # 格式替换规则:匹配(前缀)(空白字符)(后缀),替换为 前缀+后缀
    replace_rules = [
        # 1. { 与 delay 之间的空白
        (r'(\{)\s+(delay)', r'\1\2'),
        # 2. , 与 event_type 之间的空白
        (r'(,)\s+(event_type)', r'\1\2'),
        # 3. , 与 action_type 之间的空白
        (r'(,)\s+(action_type)', r'\1\2'),
        # 4. , 与 action 之间的空白
        (r'(,)\s+(action)', r'\1\2'),
        # 5. [ 与 " 之间的空白
        (r'(\[)\s+(")', r'\1\2'),
        # 6. , 与 " 之间的空白
        (r'(,)\s+(")', r'\1\2'),
        # 7. , 与 type 之间的空白
        (r'(,)\s+(type)', r'\1\2'),
        # 8. 字符串数组结尾的 , 与 ] 之间的空白(仅匹配字符串数组,不触碰对象数组结尾)
        (r'(",)\s+(\])', r'\1\2'),
        # 9. 事件对象结尾的 , 与 } 之间的空白(排除最外层],与},符合禁止合并规则)
        (r'(?<!\]),\s+(\})', r',\1'),
    ]

    processed = content
    for pattern, repl in replace_rules:
        processed = re.sub(pattern, repl, processed)
    return processed


def process_single_file(file_path):
    """单个文件完整处理流程:先格式单行化 → 再事件处理,保证识别准确"""
    # 1. 读取原文件,完整保留所有原始格式与换行
    with open(file_path, 'r', encoding='utf-8') as f:
        origin_content = f.read()

    current_content = origin_content

    # 2. 第一步:先执行格式单行化处理,把每个事件合并为完整一行
    if ENABLE_FORMAT_PROCESS:
        current_content = process_format(current_content)

    # 3. 第二步:再执行事件delay精简处理(单行事件才能正确识别字段)
    if ENABLE_DELAY_PROCESS:
        lines = current_content.splitlines(keepends=True)
        processed_lines = process_delay_events(lines)
        current_content = ''.join(processed_lines)

    # 4. 按功能组合生成对应后缀的文件名
    dir_name = os.path.dirname(file_path)
    base_name = os.path.basename(file_path)
    name, ext = os.path.splitext(base_name)

    if ENABLE_FORMAT_PROCESS and ENABLE_DELAY_PROCESS:
        suffix = '_FormatDelay'
    elif ENABLE_FORMAT_PROCESS:
        suffix = 'Format'
    elif ENABLE_DELAY_PROCESS:
        suffix = '_Delay'
    else:
        suffix = '_NoProcess'

    new_file_path = os.path.join(dir_name, f"{name}{suffix}{ext}")

    # 5. 写入新文件,不修改原文件
    with open(new_file_path, 'w', encoding='utf-8') as f:
        f.write(current_content)

    return new_file_path


def main():
    # 初始化可视化文件选择窗口
    root = tk.Tk()
    root.withdraw()

    # 弹出多选文件对话框
    selected_files = filedialog.askopenfilenames(
        title="选择要处理的JSON5文件",
        filetypes=[("JSON5文件", "*.json5"), ("所有文件", "*.*")]
    )

    if not selected_files:
        print("未选择任何文件,程序退出")
        return

    # 批量处理统计
    success_count = 0
    fail_count = 0
    total_count = len(selected_files)

    for file_path in selected_files:
        try:
            output_path = process_single_file(file_path)
            print(f"处理成功:{output_path}")
            success_count += 1
        except Exception as e:
            print(f"处理失败:{file_path} → 错误原因:{str(e)}")
            fail_count += 1

    # 输出最终统计
    print(f"\n{'='*50}")
    print(f"处理完成:共选中 {total_count} 个文件")
    print(f"成功:{success_count} 个 | 失败:{fail_count} 个")
    print(f"{'='*50}")


if __name__ == "__main__":
    main()
posted @ 2026-07-06 20:42  Li_Neo  阅读(4)  评论(0)    收藏  举报