├── .gitignore ├── AEG插件快速装载器 ├── AEG自动化辅助装载器 - 自动化 脚本管理 加载.lua ├── README.md ├── [使用文档]AEG自动化辅助装载器.docx ├── [使用文档]AEG自动化辅助装载器.pdf ├── 前置库 │ └── CX_AEG插件辅助函数库.lua └── 更新简述.md ├── ASS WE CAN-已发布版本存档 ├── ASS WE CANv1.1.1 正式版[2019年1月29日]-包含安装与使用说明 需解压.zip ├── ASS WE CAN安装及使用方法.pdf └── README.md ├── CX_AEG插件辅助函数库 ├── README.md ├── [v1.0.1dev]CX_AEG插件辅助函数库.zip └── 当前版本 │ ├── CX_AEG插件辅助函数库.lua │ └── [示例插件]CXkara_辅助函数库示例及测试.lua ├── Ckara_模版应用-已发布版本存档 ├── Ckara_模版应用_特效辅助_dev1.0.0.lua ├── Ckara_模版应用_特效辅助_dev1.1.0.lua ├── README.md └── [1.2.0dev]Ckara_模版应用_特效辅助-2020年4月13日 182810.lua ├── LICENSE ├── README.md └── 复查助手-已发布版本存档 ├── AEG复查小助手v1.1.4 Alpha[2019年12月16日].lua ├── AEG复查小助手v1.3.0 Alpha[2019年1月20日]-包含安装说明 需解压.zip ├── AEG复查小助手v1.3.2 Alpha[2020年1月27日].lua ├── AEG复查小助手v1.3.3 正式版[2019年1月29日]-包含安装与使用说明 需解压.zip ├── AEG复查小助手v1.3.4 正式版[2020年4月7日]-包含安装与使用说明 需解压.zip ├── README.md ├── [复查小助手]使用说明v2.0.0.pdf ├── [复查小助手年度更新]查轴助手v1.0.0dev.zip ├── [年度]AEG复查小助手v2.0.0 正式版[2021年1月27日]-包含安装与使用说明 需解压.zip ├── [年度]AEG复查小助手v2.0.1 正式版[2021年1月27日]-包含安装与使用说明 需解压.zip ├── 当前版本 ├── AEG自动化辅助装载器 - 自动化 脚本管理 加载.lua ├── [使用说明]AEG自动化辅助装载器.txt ├── [复查小助手]使用说明v2.0.1.docx ├── [复查小助手]使用说明v2.0.1.pdf ├── [复查小助手]安装说明v2.0.1.docx ├── [复查小助手]安装说明v2.0.1.pdf ├── 前置库 │ ├── CX_AEG插件辅助函数库.lua │ ├── Yutils.lua │ └── utils-auto4.lua ├── 复查小助手v2.0.1.lua └── 更新简述.txt └── 更新简述.md /.gitignore: -------------------------------------------------------------------------------- 1 | /ASS WE CAN 2 | /复查助手 3 | /字幕文件代理 4 | /Ckara_模版应用 -------------------------------------------------------------------------------- /AEG插件快速装载器/AEG自动化辅助装载器 - 自动化 脚本管理 加载.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 本脚本使用最基础的AEG函数装载 3 | 注:必须安装AEG辅助函数库方可正常运行 4 | 大致流程 5 | 1、测试函数库是否装载 6 | 2、如果使用自动装载,在未装载时测试权限 7 | 3、没有权限则提示使用管理员权限打开,若有权限则进行下一步 8 | 4、通过文件加载器加载指定文件到指定位置 9 | 注:如果不使用自动装载将提供打开文件夹的功能 10 | ]] 11 | -- 脚本名 12 | script_name = "AEG自动化辅助装载器" 13 | -- 脚本描述 14 | script_description = "用于辅助AEG插件、库、dll等文件的装载" 15 | -- 作者 16 | script_author = "晨轩°" 17 | 18 | -- CX插件扩展值 19 | -- 脚本签名(同一脚本签名请保持不变,签名不能含特殊字符,防止配置冲突) 20 | script_signature = "com.chenxuan.辅助装载" 21 | -- 版本号 22 | script_version = "1.0.0正式版" 23 | -- 关于 24 | script_about = [[ 25 | 用于辅助AEG插件、库、dll等文件的装载 26 | ]] 27 | -- 更新日志 28 | script_ChangeLog = [[ 29 | v1.0.0正式版 30 | 更新了更新日志 31 | ]] 32 | 33 | lfs = require("lfs") 34 | 35 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 36 | select_file_last_select_path = "" 37 | function select_file(title, wildcards, default_dir, must_exist, default_file, allow_multiple) 38 | if title == nil then title = '选择文件' end 39 | if default_file == nil then default_file = '' end 40 | if default_dir == nil then 41 | default_dir = '' 42 | if select_file_last_select_path then 43 | default_dir = select_file_last_select_path 44 | else 45 | temp_path = aegisub.decode_path('?script\\file') 46 | if temp_path ~= '?script\\file' then 47 | default_dir = temp_path 48 | end 49 | end 50 | end 51 | if wildcards == nil then wildcards = '所有文件(*)|*' end 52 | if allow_multiple == nil then allow_multiple = false end 53 | if must_exist == nil then must_exist = true end 54 | file_name = aegisub.dialog.open(title, default_file, default_dir, wildcards, allow_multiple, must_exist) 55 | if file_name then select_file_last_select_path = file_name end 56 | return file_name 57 | end 58 | -- 二进制复制文件 59 | function copyFile(sourcePath,targetPath) 60 | local rf = io.open(sourcePath,"rb") --使用“rb”打开二进制文件,如果是“r”的话,是使用文本方式打开,遇到‘0’时会结束读取 61 | local len = rf:seek("end") --获取文件长度 62 | local wf = io.open(targetPath,"wb") --用“wb”方法写入二进制文件 63 | if len ~= 0 then 64 | rf:seek("set",0)--重新设置文件索引为0的位置 65 | local data = rf:read(len) --根据文件长度读取文件数据 66 | wf:write(data) 67 | end 68 | rf:close() 69 | wf:close() 70 | 71 | end 72 | 73 | 74 | -- 环境错误检测 75 | local cx_help_libname = "CX_AEG插件辅助函数库" 76 | local cx_help = nil 77 | if (not pcall( 78 | function(libname) 79 | cx_help = require(cx_help_libname) 80 | end 81 | ,cx_help_libname 82 | )) or cx_help == nil 83 | then 84 | local function errLoadLibDealFunc(subs) 85 | local function print(msg) 86 | aegisub.debug.out(0, msg) 87 | end 88 | if cx_help ~= nil then 89 | cx_help.alert("辅助库已安装\n请重新载入脚本或重启Aegisub使改动生效") 90 | return 91 | end 92 | filename = select_file("选择CX_AEG插件辅助函数库文件","lua(*.lua)|*.lua") 93 | if filename == nil or filename == "" then 94 | print("未选择合法文件") 95 | return 96 | end 97 | print("已确认库文件路径:"..filename.."\n") 98 | print("正在检测脚本合法性...\n") 99 | -- 备份原始档案,覆盖环境执行测试 100 | local cachepath = package.path 101 | local cache_aeg = aegisub 102 | local cache_aegmin = aeg 103 | local supertable = {} 104 | --[[ 105 | setmetatable(supertable,{ 106 | __index = function(mytable, key) 107 | return mytable 108 | end 109 | ,__newindex = function(mytable, key, value) 110 | return 111 | end 112 | ,__call = function(mytable, newtable) 113 | return mytable 114 | end 115 | }) 116 | ]] 117 | if pcall( 118 | function(filename) 119 | -- 测试加载文件 120 | local fi = string.find(string.reverse(filename),"\\") 121 | package.path = package.path..";"..string.sub(filename,0,-fi).."/?.lua" 122 | local modname = string.sub(string.sub(filename,-fi + 1),0,-5) 123 | local testload = require(modname) 124 | assert(testload.aeg.exit == aegisub.cancel,"非法的辅助库文件") 125 | end, filename) 126 | then 127 | _G.aegisub = cache_aeg 128 | _G.aeg = cache_aegmin 129 | package.path = cachepath 130 | print(">>>脚本测试通过<<<\n") 131 | else 132 | _G.aegisub = cache_aeg 133 | _G.aeg = cache_aegmin 134 | package.path = cachepath 135 | print("!!!脚本测试不通过,无法执行安装!!!\n") 136 | return 137 | end 138 | libpath = aegisub.decode_path('?data\\').."automation\\include\\" 139 | print("安装路径:"..libpath.."\n") 140 | print("尝试安装...\n") 141 | if pcall(copyFile,filename,libpath..cx_help_libname..".lua") 142 | then 143 | print(">>>安装成功!<<<\n") 144 | else 145 | print("!!!脚本安装失败,可能权限不足,请尝试管理员权限打开Aegisub!!!\n") 146 | return 147 | end 148 | if (not pcall( 149 | function(libname) 150 | cx_help = require(cx_help_libname) 151 | end 152 | ,cx_help_libname 153 | )) or cx_help == nil 154 | then 155 | print("!!!未知原因错误,无法载入库文件!!!") 156 | return 157 | end 158 | print("函数辅助库文件加载成功!请重新加载脚本或者重启Aegisub以应用修改\n") 159 | cx_help.alert("安装完成,欢迎使用!") 160 | end 161 | aegisub.register_macro("自动装载 - 未初始化","装载器未初始化完成",errLoadLibDealFunc) 162 | 163 | 164 | -- 未成功加载库的返回 165 | return 166 | end 167 | 168 | 169 | 170 | -- 创建合适的函数环境 171 | cx_help.table.merge(_G,cx_help) 172 | setting.setLevel(0) 173 | function file_exists(path) 174 | local file = io.open(path, "rb") 175 | if file then file:close() end 176 | return file ~= nil 177 | end 178 | function loadlib() 179 | filename = select_file("选择库文件","lua(*.lua)|*.lua") 180 | if filename == nil or filename == "" then 181 | return 182 | end 183 | local fi = string.find(string.reverse(filename),"\\") 184 | 185 | if not confirm("你确定要安装 "..string.sub(string.sub(filename,-fi + 1),0,-5).." 吗?") then 186 | return 187 | end 188 | print("已确认库文件路径:"..filename.."\n") 189 | print("正在测试脚本合法性...\n") 190 | -- 备份原始档案,覆盖环境执行测试 191 | local cachepath = package.path 192 | local cache_aeg = aegisub 193 | local cache_aegmin = aeg 194 | local supertable = {} 195 | --[[ 196 | setmetatable(supertable,{ 197 | __index = function(mytable, key) 198 | return mytable 199 | end 200 | ,__newindex = function(mytable, key, value) 201 | return 202 | end 203 | ,__call = function(mytable, newtable) 204 | return mytable 205 | end 206 | }) 207 | ]] 208 | _G.aegisub = supertable 209 | _G.aeg = supertable 210 | cx_help.aeg = supertable 211 | if pcall( 212 | function(filename) 213 | -- 测试加载文件 214 | local fi = string.find(string.reverse(filename),"\\") 215 | package.path = package.path..";"..string.sub(filename,0,-fi).."/?.lua" 216 | local modname = string.sub(string.sub(filename,-fi + 1),0,-5) 217 | local testload = require(modname) 218 | end, filename) 219 | then 220 | _G.aegisub = cache_aeg 221 | _G.aeg = cache_aegmin 222 | cx_help.aeg = cache_aegmin 223 | package.path = cachepath 224 | print(">>>脚本测试通过<<<\n") 225 | else 226 | _G.aegisub = cache_aeg 227 | _G.aeg = cache_aegmin 228 | cx_help.aeg = cache_aegmin 229 | package.path = cachepath 230 | print("!!!脚本测试不通过,无法执行安装!!!\n") 231 | return 232 | end 233 | 234 | libpath = aeg.path.global_lib 235 | print("安装路径:"..libpath.."\n") 236 | print("尝试安装...\n") 237 | 238 | local targetPath = libpath..string.sub(filename,-fi + 1) 239 | if file_exists(targetPath) then 240 | if not confirm("文件已存在,是否覆盖?") then 241 | print("已取消安装...\n") 242 | return 243 | end 244 | end 245 | if pcall(copyFile,filename,targetPath) 246 | then 247 | print(">>>安装成功!<<<\n") 248 | else 249 | print("!!!脚本安装失败,可能权限不足,请尝试管理员权限打开Aegisub!!!\n") 250 | return 251 | end 252 | print("请重新加载脚本或者重启Aegisub以应用修改\n") 253 | end 254 | 255 | function os_openpath(path) 256 | os.execute("explorer "..path) 257 | end 258 | 259 | -- 无错误导入(参数 库名),出错返回nil 260 | function noerrRequire(libname) 261 | local res = nil 262 | pcall( 263 | function (reqname) 264 | -- 测试加载文件 265 | res = require(reqname) 266 | end, libname) 267 | return res 268 | end 269 | 270 | -- 载入测试 271 | local Yutils = noerrRequire("Yutils") 272 | 273 | -- 环境检测菜单 274 | envTestMenu = { 275 | menus.menu("辅助函数库",nil,function() end,nil,function () return true end) 276 | ,menus.menu("Yutils",nil,function() end,nil,function () return Yutils ~= nil end) 277 | 278 | } 279 | 280 | function loadscript() 281 | filename = select_file("选择脚本文件","lua(*.lua)|*.lua") 282 | if filename == nil or filename == "" then 283 | return 284 | end 285 | local fi = string.find(string.reverse(filename),"\\") 286 | if not confirm("你确定要安装 "..string.sub(string.sub(filename,-fi + 1),0,-5).." 吗?") then 287 | return 288 | end 289 | print("已确认脚本文件路径:"..filename.."\n") 290 | 291 | libpath = aeg.path.global_script 292 | print("安装路径:"..libpath.."\n") 293 | print("尝试安装...\n") 294 | 295 | local targetPath = libpath..string.sub(filename,-fi + 1) 296 | if file_exists(targetPath) then 297 | if not confirm("文件已存在,是否覆盖?") then 298 | print("已取消安装...\n") 299 | return 300 | end 301 | end 302 | if pcall(copyFile,filename,targetPath) 303 | then 304 | print(">>>安装成功!<<<\n") 305 | else 306 | print("!!!脚本安装失败,可能权限不足,请尝试管理员权限打开Aegisub!!!\n") 307 | return 308 | end 309 | end 310 | 311 | function removelib() 312 | -- 遍历脚本目录 313 | local scriptpath = aeg.path.global_lib 314 | local llist = {} 315 | for path in lfs.dir(scriptpath) do 316 | if string.sub(path,-4) == ".lua" then 317 | local unit = string.sub(path,0,-5) 318 | table.insert(llist,unit) 319 | end 320 | end 321 | config = { 322 | {class="label",label ="库列表",x=0,y=0,width = 5,height = 1} 323 | ,{class = "dropdown",value = "",items = llist,name = "dr",hint="选择需要卸载的插件",x=0,y=1,width = 5,height = 1} 324 | } 325 | -- 显示对话框 326 | btn, btnresult = aegisub.dialog.display(config) 327 | removename = btnresult.dr 328 | if btn == false or removename == "" then 329 | return 330 | end 331 | if not confirm("你确定要删除 "..removename.." 吗?") then 332 | return 333 | end 334 | filepath = aeg.path.global_lib..removename..".lua" 335 | --DD("删除路径:",filepath) 336 | --DD("文件存在性检测:",file_exists(filepath)) 337 | res,err = os.remove(filepath) 338 | if res == nil then 339 | println("删除失败,可能权限不足,请尝试管理员权限打开Aegisub") 340 | println("错误原文:",err) 341 | return 342 | end 343 | alert("删除成功") 344 | end 345 | function removescript() 346 | -- 遍历脚本目录 347 | local scriptpath = aeg.path.global_script 348 | local slist = {} 349 | for path in lfs.dir(scriptpath) do 350 | if string.sub(path,-4) == ".lua" then 351 | local unit = string.sub(path,0,-5) 352 | table.insert(slist,unit) 353 | end 354 | end 355 | config = { 356 | {class="label",label ="自动化脚本列表",x=0,y=0,width = 5,height = 1} 357 | ,{class = "dropdown",value = "",items = slist,name = "dr",hint="选择需要卸载的插件",x=0,y=1,width = 5,height = 1} 358 | } 359 | -- 显示对话框 360 | btn, btnresult = aegisub.dialog.display(config) 361 | removename = btnresult.dr 362 | if btn == false or removename == "" then 363 | return 364 | end 365 | if not confirm("你确定要删除 "..removename.." 吗?") then 366 | return 367 | end 368 | filepath = aeg.path.global_script..removename..".lua" 369 | --DD("删除路径:",filepath) 370 | --DD("文件存在性检测:",file_exists(filepath)) 371 | res,err = os.remove(filepath) 372 | if res == nil then 373 | println("删除失败,可能权限不足,请尝试管理员权限打开Aegisub") 374 | println("错误原文:",err) 375 | return 376 | end 377 | alert("删除成功") 378 | end 379 | 380 | 381 | -- 菜单结构 382 | varmenu = { 383 | menus.next("当前已安装环境" 384 | ,envTestMenu 385 | ) 386 | ,menus.next("打开AEG目录" 387 | ,{ 388 | menus.menu("根目录",nil,function() os_openpath(aeg.path.data) end,nil,nil) 389 | ,menus.menu("脚本目录",nil,function() os_openpath(aeg.path.global_script) end,nil,nil) 390 | ,menus.menu("库目录",nil,function() os_openpath(aeg.path.global_lib) end,nil,nil) 391 | ,menus.menu("滤镜目录(DLL)",nil,function() os_openpath(aeg.path.data.."csri\\") end,nil,nil) 392 | ,menus.menu("自动保存目录",nil,function() os_openpath(aeg.path.autosave) end,nil,nil) 393 | ,menus.menu("自动备份目录",nil,function() os_openpath(aeg.path.autoback) end,nil,nil) 394 | ,menus.menu("辅助函数库配置文件目录",nil,function() os_openpath(aeg.path.config) end,nil,nil) 395 | } 396 | ) 397 | ,menus.menu("加载库",nil,loadlib,nil,nil) 398 | ,menus.menu("卸载库",nil,removelib,nil,nil) 399 | ,menus.menu("安装自动化脚本(全局)",nil,loadscript,nil,nil) 400 | ,menus.menu("卸载自动化脚本(全局)",nil,removescript,nil,nil) 401 | ,menus.about() 402 | } 403 | 404 | -- 应用菜单设置 405 | tools.automenu("自动装载",varmenu) 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /AEG插件快速装载器/README.md: -------------------------------------------------------------------------------- 1 | # AEG自动化辅助装载器 2 | 3 | 一个很纯净,很便捷的装载器。 4 | 5 | 前置库:CX_AEG插件辅助函数库.lua 6 | 7 | 本体:AEG自动化辅助装载器 - 自动化 脚本管理 加载.lua 8 | 9 | ## 功能 10 | 11 | 支持以单文件加载的形式运行 12 | 13 | > 使用自动化脚本管理器进行加载即可运行 14 | 15 | 支持显示当前已安装的环境 16 | 17 | > 目前仅支持 Yutils的显示 如果有需要添加的库请提交issue 18 | 19 | 支持目录快速跳转 20 | 21 | > 根目录、脚本目录、库目录、滤镜目录(DLL)、自动保存目录、自动备份目录及辅助函数库配置文件目录 22 | 23 | 支持加载与卸载库 24 | 25 | 支持加载与卸载全局脚本 26 | 27 | 28 | 29 | 具体使用方式可以参考当前目录下的PDF或WORD文档 -------------------------------------------------------------------------------- /AEG插件快速装载器/[使用文档]AEG自动化辅助装载器.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/AEG插件快速装载器/[使用文档]AEG自动化辅助装载器.docx -------------------------------------------------------------------------------- /AEG插件快速装载器/[使用文档]AEG自动化辅助装载器.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/AEG插件快速装载器/[使用文档]AEG自动化辅助装载器.pdf -------------------------------------------------------------------------------- /AEG插件快速装载器/前置库/CX_AEG插件辅助函数库.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | AEG插件辅助函数库 3 | 版权所属:晨轩°(QQ3309003591) 4 | 接口文档 5 | ]] 6 | -- 模块基础信息、接口及常量定义 7 | -- 版本号 8 | lib_version = "1.0.1dev" 9 | -- 更新日志 10 | lib_ChangeLog = [[ 11 | 1.0.1dev 12 | 支持了多选菜单的双向绑定 13 | ]] 14 | 15 | local lfs = require "lfs" 16 | local re = require 'aegisub.re' 17 | local LEVEL = { 18 | FATAL = 0 19 | ,ERROR = 1 20 | ,WARNING = 2 21 | ,INFO = 3 -- AEG默认等级 22 | ,DEBUG = 4 23 | ,TRACK = 5 24 | } 25 | local print_level = LEVEL.INFO -- 默认输出等级,用于输出文本 26 | local close_configwrite = false -- 关闭配置文件写入(通过本函数库执行的写入都将被禁止) 27 | -- 设置接口 28 | function setLevel(level) 29 | if type(level) ~= "number" and ( level >= 1 and level <= 5 ) then 30 | error("设置的等级必须为整数,且在1~5之间。") 31 | end 32 | print_level = math.modf(level) 33 | end 34 | function getLevel() 35 | return print_level 36 | end 37 | function setCloseConfigWrite(b) 38 | close_configwrite = (b == true) 39 | end 40 | function getCloseConfigWrite() 41 | return close_configwrite 42 | end 43 | 44 | -- 设置表 45 | local setting = { 46 | LEVEL = LEVEL 47 | ,setLevel = setLevel -- 设置默认输出等级 48 | ,getLevel = getLevel 49 | ,setCloseConfigWrite = setCloseConfigWrite -- 设置关闭配置写入 50 | ,getCloseConfigWrite = getCloseConfigWrite 51 | } 52 | -- AEG兼容定义(用于使插件在AEG中拥有与IDE相同的响应) 53 | -- 等级输出 54 | function levelout(level,vaule) 55 | aegisub.debug.out(level, vaule) 56 | end 57 | -- 标准输出 58 | function standout(vaule) 59 | aegisub.debug.out(print_level, vaule) 60 | end 61 | -- 打印所有能打印的类型 62 | function valueToStr(value) 63 | if value == nil then 64 | return "nil" 65 | end 66 | if type(value) ~= "table" then 67 | return tostring(value) 68 | end 69 | res = "" 70 | -- print覆盖 71 | local function print(val) 72 | if val == nil then 73 | res = res .. 'nil'..'\n' 74 | else 75 | res = res .. tostring(val)..'\n' 76 | end 77 | end 78 | -- 好用的table输出函数 79 | local function print_r(t) 80 | local print_r_cache={} 81 | local function sub_print_r(t,indent) 82 | if (print_r_cache[tostring(t)]) then 83 | print(indent.."*"..tostring(t)) 84 | else 85 | print_r_cache[tostring(t)]=true 86 | if (type(t)=="table") then 87 | for pos,val in pairs(t) do 88 | pos = tostring(pos) 89 | if (type(val)=="table") then 90 | print(indent.."["..pos.."] => "..tostring(t).." {") 91 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 92 | print(indent..string.rep(" ",string.len(pos)+6).."}") 93 | elseif (type(val)=="string") then 94 | print(indent.."["..pos..'] => "'..val..'"') 95 | else 96 | print(indent.."["..pos.."] => "..tostring(val)) 97 | end 98 | end 99 | else 100 | print(indent..tostring(t)) 101 | end 102 | end 103 | end 104 | if (type(t)=="table") then 105 | print(tostring(t).." {") 106 | sub_print_r(t," ") 107 | print("}") 108 | else 109 | sub_print_r(t," ") 110 | end 111 | end 112 | -- 运行函数 113 | print_r(value) 114 | -- 打印结果 115 | return string.sub(res,0,-2) 116 | end 117 | 118 | -- 短输出 119 | function print(...) 120 | local arg = {...} 121 | maxi = 1 122 | for i,v in pairs(arg) do 123 | if i > maxi then maxi = i end 124 | end 125 | printResult = "" 126 | for i = 1,maxi do 127 | printResult = printResult .. valueToStr(arg[i]) 128 | end 129 | standout(printResult) 130 | end 131 | function println(...) 132 | local arg = {...} 133 | maxi = 1 134 | for i,v in pairs(arg) do 135 | if i > maxi then maxi = i end 136 | end 137 | arg[maxi + 1] = "\n" 138 | print(unpack(arg)) 139 | end 140 | -- 完整输出 141 | function var_print(...) 142 | local arg = {...} 143 | maxi = 1 144 | for i,v in pairs(arg) do 145 | if i > maxi then maxi = i end 146 | end 147 | printResult = "" 148 | for i = 1,maxi do 149 | printResult = printResult .. valueToStr(arg[i]) 150 | end 151 | standout(printResult) 152 | end 153 | function var_println(...) 154 | local arg = {...} 155 | maxi = 1 156 | for i,v in pairs(arg) do 157 | if i > maxi then maxi = i end 158 | end 159 | arg[maxi + 1] = "\n" 160 | var_print(unpack(arg)) 161 | end 162 | 163 | --[[ 其它工具集 ]] 164 | -- 创建偏函数 165 | function functool_partial(func,...) 166 | local arg = {...} 167 | local getMerge = function(arg2) 168 | res = {} 169 | for _,v in pairs(arg) do 170 | table.insert(res,v) 171 | end 172 | for _,v in pairs(arg2) do 173 | table.insert(res,v) 174 | end 175 | return res 176 | end 177 | return function(...) 178 | local arg2 = {...} 179 | return func(unpack(getMerge(arg2))) 180 | end 181 | end 182 | 183 | 184 | --[[ 185 | 扩展环境 186 | ]] 187 | -- 合并表 188 | function table.merge(source,addtable) 189 | for k,v in pairs(addtable) do 190 | source[k] = v 191 | end 192 | return source 193 | end 194 | -- 去除首尾空格 195 | local str_trim_expr_pre = re.compile([[^\s+]],re.NOSUB,re.NO_MOD_M) 196 | local str_trim_expr_end = re.compile([[\s+$]],re.NOSUB,re.NO_MOD_M) 197 | function string.trim(str) 198 | out_str, rep_count = str_trim_expr_pre:sub(str,'') 199 | out_str, rep_count1 = str_trim_expr_end:sub(out_str,'') 200 | if not rep_count then rep_count = 0 end 201 | if not rep_count1 then rep_count1 = 0 end 202 | rep_count = rep_count + rep_count1 203 | return out_str 204 | end 205 | 206 | 207 | --[[ 208 | 缩写集 209 | ]] 210 | 211 | 212 | -- 缩写aegisub表 213 | local aeg = aegisub 214 | local aegmin = {} 215 | local aegPath = {} 216 | aeg.regMacro = aegisub.register_macro 217 | aeg.regFilter = aegisub.register_filter 218 | aeg.levelout = levelout 219 | aeg.standout = standout 220 | aeg.exit = aegisub.cancel 221 | aeg.path = aegPath 222 | 223 | setmetatable(aeg, { 224 | __index = function(t, key) 225 | if aegisub.progress ~= nil then 226 | aegmin.setTitle = aegisub.progress.title 227 | aegmin.setProgress = aegisub.progress.set 228 | aegmin.setTask = aegisub.progress.task 229 | aegmin.cancelled = aegisub.progress.is_cancelled 230 | -- 设置还原点 231 | aegmin.setUndo = aegisub.set_undo_point 232 | -- 等待AEG响应,同时判断是否取消执行,用户取消执行将结束脚本,参数为结束运行钩子函数 233 | aegmin.waitAeg = function (func) 234 | if aegisub.progress.is_cancelled() then 235 | if type(func) == "function" then 236 | func() 237 | end 238 | end 239 | end 240 | end 241 | return aegmin[key] 242 | end 243 | }) 244 | -- 获取路径说明,无法获取时返回nil 245 | setmetatable(aegPath, { 246 | __index = function(t, key) 247 | if key == "data" then 248 | -- 存储应用数据的位置。在Windows下是指Aegisub安装目录(.exe的位置)。在Mac OS X下被包含在应用包里。在其他类POSIX系统下的目录为 $prefix/share/aegisub/. 249 | return aegisub.decode_path('?data\\') 250 | 251 | elseif key == "user" then 252 | -- 存储用户数据的位置,例如配置文件,自动备份文件和其他附加的东西。在Windows下,这个路径是 %APPDATA%\Aegisub\; 在Mac OS X下,这个路径是 $HOME/Library/ApplicationSupport/Aegisub/; 在其他类OSIX系统下,这个路径是 $HOME/.aegisub/; 在便携版Aegisub中这个目录是 ?data。 253 | return aegisub.decode_path('?user\\') 254 | 255 | elseif key == "temp" then 256 | -- 系统临时文件目录。音频缓存和临时字幕文件都存储在这个位置。 257 | return aegisub.decode_path('?temp') 258 | 259 | elseif key == "local" or key == "cache" then 260 | -- 本地用户设置路径。存储运行缓存文件的位置,例如FFMS2索引和字体配置缓存。Windows下为 %LOCALAPPDATA%\Aegisub 其他系统是 ?user。 261 | return aegisub.decode_path('?local\\') 262 | 263 | elseif key == "script" then 264 | -- 只有当你打开一个已经保存在本地的字幕文件时才有作用,为该字幕文件的保存的位置。 265 | text = aegisub.decode_path('?script\\') 266 | if text == '?script\\' then text = nil end 267 | return text 268 | 269 | elseif key == "video" then 270 | -- 只有读取本地视频后才有作用,为当前读取视频文件的路径,注意读取空白视频时是无法使用该路径的。 271 | text = aegisub.decode_path('?video\\') 272 | if text == '?video\\' then text = nil end 273 | return text 274 | 275 | elseif key == "audio" then 276 | -- 只有读取本地音频后才有作用,为当前读取音频文件的路径,注意读取空白音频时是无法使用该路径的。 277 | text = aegisub.decode_path('?audio\\') 278 | if text == '?audio\\' then text = nil end 279 | return text 280 | -- 扩展路径 281 | elseif key == "config" then 282 | -- 配置文件路径 283 | text = aegisub.decode_path('?user\\').."script_config\\" 284 | lfs.mkdir(text) 285 | return text 286 | 287 | elseif key == "global_script" then 288 | -- 自动化脚本文件路径 289 | text = aegisub.decode_path('?data\\').."automation\\autoload\\" 290 | return text 291 | 292 | elseif key == "global_lib" then 293 | -- 库文件路径 294 | text = aegisub.decode_path('?data\\').."automation\\include\\" 295 | return text 296 | 297 | elseif key == "autosave" then 298 | -- 自动保存路径 299 | text = aegisub.decode_path('?user\\').."autosave\\" 300 | return text 301 | 302 | elseif key == "autoback" then 303 | -- 自动备份路径 304 | text = aegisub.decode_path('?user\\').."autoback\\" 305 | return text 306 | 307 | end 308 | return nil 309 | end 310 | }) 311 | 312 | -- 函数定义 313 | --[[ 314 | 显示集 315 | ]] 316 | 317 | -- 显示一个简单的窗口,参数(提示信息[,类型标识[,默认值]]) 318 | --[[ 319 | 类型标识(返回类型): 320 | 0-提示框(nil),提示需要每行尽可能短(不超过9个字) 321 | 1-确认取消框(bool) 322 | 2-单行文本输入框(string or nil) 323 | 3-单行整数输入框(number or nil) 324 | 4-单行小数输入框(number or nil) 325 | 注意:整数与小数输入有误时不会限制或报错,可能得到奇怪的结果。 326 | ]] 327 | function QuickWindow(msg,type_num,default) 328 | local config = {} -- 窗口配置 329 | local result = nil -- 返回结果 330 | local buttons = nil 331 | local button_ids = nil 332 | if type(msg) ~= 'string' then 333 | error('display.confirm参数错误-1,提示信息必须存在且为文本类型!',2) 334 | end 335 | if type_num == nil then type_num = 0 end 336 | if type(type_num) ~= 'number' then 337 | -- db.var_export(type_num) 338 | error('display.confirm参数错误-2,类型标识必须是数值!',2) 339 | end 340 | 341 | if type_num == 0 then 342 | config = { 343 | {class="label", label=msg, x=0, y=0,width=8} 344 | } 345 | buttons = {aegisub.gettext'Yes'} 346 | button_ids = {ok = aegisub.gettext'Yes'} 347 | elseif type_num == 1 then 348 | config = { 349 | {class="label", label=msg, x=0, y=0,width=12} 350 | } 351 | elseif type_num == 2 then 352 | if default == nil then default = '' end 353 | config = { 354 | {class="label", label=msg, x=0, y=0,width=12}, 355 | {class="edit", name='text',text=tostring(default), x=0, y=1,width=12} 356 | } 357 | elseif type_num == 3 then 358 | if default == nil then default = 0 end 359 | if type(type_num) ~= 'number' then 360 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 361 | end 362 | config = { 363 | {class="label", label=msg, x=0, y=0,width=12}, 364 | {class="intedit", name='int',value=default, x=0, y=1,width=12} 365 | } 366 | elseif type_num == 4 then 367 | if default == nil then default = 0 end 368 | if type(type_num) ~= 'number' then 369 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 370 | end 371 | config = { 372 | {class="label", label=msg, x=0, y=0,width=12}, 373 | {class="floatedit", name='float',value=default, x=0, y=1,width=12} 374 | } 375 | elseif type_num == 5 then 376 | if default == nil then default = '' end 377 | if type(type_num) ~= 'number' then 378 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 379 | end 380 | config = { 381 | {class="label", label=msg, x=0, y=0,width=12}, 382 | {class="textbox", name='textbox',text=tostring(default), x=0, y=1,width=15,height=10} 383 | } 384 | else 385 | error('display.confirm参数错误,无效的类型标识!',2) 386 | end 387 | 388 | -- 显示对话框 389 | btn, btnresult = aegisub.dialog.display(config,buttons,button_ids) 390 | 391 | -- 处理并返回结果 392 | if type_num == 0 then 393 | result = nil 394 | elseif type_num == 1 then 395 | if btn ~= false then 396 | result = true 397 | else 398 | result = false 399 | end 400 | elseif type_num == 2 then 401 | if btn ~= false then 402 | result = btnresult.text 403 | end 404 | elseif type_num == 3 then 405 | if btn ~= false then 406 | result = btnresult.int 407 | end 408 | elseif type_num == 4 then 409 | if btn ~= false then 410 | result = btnresult.float 411 | end 412 | elseif type_num == 5 then 413 | if btn ~= false then 414 | result = btnresult.textbox 415 | end 416 | end 417 | -- debug.var_export(result) 418 | return result 419 | end 420 | 421 | -- JS拟态提示框 422 | -- 警告框 参数 显示的消息 | 返回值 nil 423 | function alert(msg) 424 | return QuickWindow(msg,0,"") 425 | end 426 | -- 确认框 参数 显示的消息 | 返回值 true/false 427 | function confirm(msg) 428 | return QuickWindow(msg,1,"") 429 | end 430 | -- 文本输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 431 | function prompt(msg,defval) 432 | return QuickWindow(msg,2,defval) 433 | end 434 | input = prompt 435 | 436 | -- 文本域输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 437 | function inputTextArea(msg,defval) 438 | return QuickWindow(msg,5,defval) 439 | end 440 | 441 | -- 整数输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 442 | function inputInt(msg,defval) 443 | return QuickWindow(msg,3,defval) 444 | end 445 | -- 小数输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 446 | function inputFloat(msg,defval) 447 | return QuickWindow(msg,4,defval) 448 | end 449 | 450 | 451 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 452 | select_file_last_select_path = "" 453 | function select_file(title, wildcards, default_dir, must_exist, default_file, allow_multiple) 454 | if title == nil then title = '选择文件' end 455 | if default_file == nil then default_file = '' end 456 | if default_dir == nil then 457 | default_dir = '' 458 | if select_file_last_select_path then 459 | default_dir = select_file_last_select_path 460 | else 461 | temp_path = aegisub.decode_path('?script\\file') 462 | if temp_path ~= '?script\\file' then 463 | default_dir = temp_path 464 | end 465 | end 466 | end 467 | if wildcards == nil then wildcards = '所有文件(*)|*' end 468 | if allow_multiple == nil then allow_multiple = false end 469 | if must_exist == nil then must_exist = true end 470 | file_name = aegisub.dialog.open(title, default_file, default_dir, wildcards, allow_multiple, must_exist) 471 | if file_name then select_file_last_select_path = file_name end 472 | return file_name 473 | end 474 | 475 | local display = { 476 | -- 显示一个简单的提示窗口,参数(提示信息[,类型标识[,默认值]]) 477 | QuickWindow = QuickWindow 478 | ,alert = alert -- 警告框 参数 显示的消息 | 返回值 nil 479 | ,confirm = confirm -- 确认框 参数 显示的消息 | 返回值 true/false 480 | ,inputTextArea = inputTextArea 481 | ,prompt = prompt -- 文本输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 482 | ,input = input 483 | ,inputInt = inputInt 484 | ,inputFloat = inputFloat 485 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 返回 文件路径 486 | ,select_file = select_file 487 | } 488 | 489 | --[[ 490 | 字幕行工具 491 | ]] 492 | function subs_getFirstDialogue(subs) 493 | for i = 1,#subs do 494 | if subs[i].class == 'dialogue' then 495 | return i 496 | end 497 | end 498 | end 499 | function subs_getStyles(subs) 500 | styles = {cout_n=0} -- 字幕行 501 | infos = {cout_n=0} -- 信息行 502 | unknows = {cout_n=0} -- 未知行 503 | first_dialogue = nil 504 | for i = 1,#subs do 505 | line = subs[i] 506 | if line.class == 'style' then 507 | styles.cout_n = styles.cout_n + 1 508 | styles[line.name] = { 509 | index = i 510 | ,line = line 511 | } 512 | elseif line.class == 'info' then 513 | infos.cout_n = infos.cout_n + 1 514 | infos[line.key:lower()] = { 515 | index = i 516 | ,line = line 517 | ,key = line.key 518 | ,value = line.value 519 | } 520 | elseif line.class == 'unknown' then 521 | unknows.cout_n = unknows.cout_n + 1 522 | table.insert(unknown,{ 523 | index = i 524 | ,line = line 525 | }) 526 | elseif line.class == 'dialogue' then 527 | first_dialogue = i 528 | break 529 | end 530 | end 531 | return styles,first_dialogue,infos,unknows 532 | end 533 | function subs_collect_head(subs) 534 | styles,first_dialogue,infos,unknows = subs_getStyles(subs) 535 | return { 536 | firstDialogue = first_dialogue 537 | ,styles = styles 538 | ,infos = infos 539 | ,unknows = unknows 540 | } 541 | end 542 | function subs_pre_deal(subs) 543 | --[[ 544 | 代理原subs对象 545 | 解决各种不爽的问题,尽可能降低因操作subs导致的AEG崩溃可能 546 | ]] 547 | heads = subs_collect_head(subs) 548 | local function getIter(tab,start_i,end_i) 549 | local si = start_i 550 | local ni = si 551 | local ei = end_i 552 | return function () 553 | ni = ni + 1 554 | if (ni - 1) <= ei then 555 | return ni - si,tab[ni - 1] 556 | end 557 | end 558 | end 559 | local function getCollectIter(tab) 560 | local tabiter = pairs(tab) 561 | local k = nil 562 | return function () 563 | k,v = tabiter(tab,k) 564 | if k == "cout_n" then 565 | k,v = tabiter(tab,k) 566 | end 567 | if k ~= nil then 568 | return v.index,v.line 569 | end 570 | end 571 | end 572 | proxySubs = { 573 | subs = subs 574 | ,heads = heads 575 | ,otherData = { 576 | -- 暂未确定 577 | } 578 | -- 获取原始行标 579 | ,getSourceIndex = function (i) 580 | return i + proxySubs.heads.firstDialogue - 1 581 | end 582 | -- 获取重定向行标 583 | ,getIndex = function (i) 584 | return i - proxySubs.heads.firstDialogue - 1 585 | end 586 | -- 兼容定义 587 | ,raw_insert = subs.insert -- 插入 588 | ,raw_delete = subs.delete -- 删除 589 | ,raw_deleterange = subs.deleterange -- 范围删除 590 | ,raw_append = subs.append -- 追加 591 | -- 重定向定义 592 | ,insert = function (i,...) 593 | if i < 0 then 594 | error("Out of bounds",2) 595 | end 596 | i = i + proxySubs.heads.firstDialogue - 1 597 | if i > #(proxySubs.subs) then 598 | error("Out of bounds",2) 599 | end 600 | return subs.insert(i,...) 601 | end 602 | ,delete = function (...) 603 | local args = {...} 604 | for i = 1, #args do 605 | if args[i] < 0 then 606 | error("Out of bounds",2) 607 | end 608 | args[i] = args[i] + proxySubs.heads.firstDialogue - 1 609 | if args[i] > #(proxySubs.subs) then 610 | error("Out of bounds",2) 611 | end 612 | end 613 | return subs.delete(unpack(args)) 614 | end 615 | ,deleterange = function (first,last) 616 | if first < 0 or last < 0 then 617 | error("Error delete range",2) 618 | end 619 | first = first + proxySubs.heads.firstDialogue - 1 620 | last = last + proxySubs.heads.firstDialogue - 1 621 | if first > #(proxySubs.subs) or last > #(proxySubs.subs) then 622 | error("Out of bounds",2) 623 | end 624 | 625 | if first > last then 626 | return 627 | end 628 | return subs.deleterange( 629 | first 630 | ,last 631 | ) 632 | end 633 | 634 | ,append = function (...) 635 | local args = {...} 636 | for _,v in pairs(args) do 637 | if type(v) ~= "table" or v.class ~= "dialogue" then 638 | error("插入了不合法的对话行",2) 639 | end 640 | end 641 | return subs.append(unpack(args)) 642 | end 643 | 644 | } 645 | setmetatable(proxySubs,{ 646 | __index = function (mytable, key) 647 | -- 元素数量数据 648 | if key == "n" then 649 | return #rawget(mytable,"subs") 650 | elseif key == "sn" then -- 字幕数 651 | return rawget(mytable,"heads").styles.cout_n 652 | elseif key == "in" or key == "i_n" then -- 信息行数量 653 | return rawget(mytable,"heads").infos.cout_n 654 | elseif key == "dn" then -- 对话行数量 655 | return #rawget(mytable,"subs") - rawget(mytable,"heads").firstDialogue + 1 656 | elseif key == "fd" then -- 对话行数量 657 | return rawget(mytable,"heads").firstDialogue 658 | 659 | -- 收集的表数据 660 | elseif key == "styles" then -- 样式行表 661 | return rawget(mytable,"heads").styles 662 | elseif key == "infos" then -- 信息行表 663 | return rawget(mytable,"heads").infos 664 | -- 迭代器 665 | elseif key == "InfosIter" or key == "infosIter" or key == "II" then -- 信息行表迭代器 666 | return getCollectIter(rawget(mytable,"heads").infos) 667 | elseif key == "StylesIter" or key == "stylesIter" or key == "SI" then -- 样式行表迭代器 668 | return getCollectIter(rawget(mytable,"heads").styles) 669 | elseif key == "DialogueIter" or key == "dialogueIter" or key == "DI" then -- 对话行表迭代器 670 | local tsubs = rawget(mytable,"subs") 671 | return getIter(tsubs,rawget(mytable,"heads").firstDialogue,#tsubs) 672 | end 673 | 674 | -- 对话行设置重定向 675 | if type(key) == "number" then 676 | if key <= 0 then 677 | error("Out of bounds",2) 678 | end 679 | key = key + rawget(mytable,"heads").firstDialogue - 1 680 | return rawget(mytable,"subs")[key] 681 | end 682 | return rawget(mytable,"subs")[key] 683 | end 684 | ,__newindex = function (mytable, key, value) 685 | -- 设置新的值都导向subs 686 | -- 对话行设置重定向 687 | if type(key) == "number" then 688 | if key <= 0 then 689 | error("Out of bounds",2) 690 | end 691 | if type(value) ~= "table" or value.class ~= "dialogue" then 692 | error("使用了不合法的对话行",2) 693 | end 694 | key = key + rawget(mytable,"heads").firstDialogue - 1 695 | end 696 | rawget(mytable,"subs")[key] = value 697 | if type(key) == "number" and key < heads.firstDialogue then 698 | -- 如果设置的值小于第一行对话行位置则重新收集行信息 699 | rawset(mytable,"heads",subs_collect_head(rawget(mytable,"subs"))) 700 | end 701 | end 702 | }) 703 | return proxySubs 704 | end 705 | --[[ 706 | 字幕辅助表 707 | ]] 708 | local subsTool = { 709 | -- 使用subs字幕对象 收集样式信息表 并返回样式表与首个dialogue行的下标 710 | getStyles = subs_getStyles 711 | -- 获取首个dialogue行的下标 712 | ,getFirstDialogue = subs_getFirstDialogue 713 | -- 字幕行预处理 714 | ,presubs = subs_pre_deal 715 | } 716 | 717 | 718 | 719 | --[[ 720 | 其它工具集 721 | ]] 722 | -- 序列化 723 | function tool_serialize(obj) 724 | local lua = "" 725 | local t = type(obj) 726 | if t == "number" then 727 | lua = lua .. obj 728 | elseif t == "boolean" then 729 | lua = lua .. tostring(obj) 730 | elseif t == "string" then 731 | lua = lua .. string.format("%q", obj) 732 | elseif t == "table" then 733 | lua = lua .. "{" 734 | for k, v in pairs(obj) do 735 | lua = lua .. "[" .. tool_serialize(k) .. "]=" .. tool_serialize(v) .. "," 736 | end 737 | local metatable = getmetatable(obj) 738 | if metatable ~= nil and type(metatable.__index) == "table" then 739 | for k, v in pairs(metatable.__index) do 740 | lua = lua .. "[" .. tool_serialize(k) .. "]=" .. tool_serialize(v) .. "," 741 | end 742 | end 743 | lua = lua .. "}" 744 | elseif t == "nil" then 745 | return nil 746 | else 747 | error("can not serialize a " .. t .. " type.") 748 | end 749 | return lua 750 | end 751 | -- 反序列化 752 | function tool_unserialize(lua) 753 | local t = type(lua) 754 | if t == "nil" or lua == "" then 755 | return nil 756 | elseif t == "number" or t == "string" or t == "boolean" then 757 | lua = tostring(lua) 758 | else 759 | error("can not unserialize a " .. t .. " type.") 760 | end 761 | lua = "return " .. lua 762 | local func = loadstring(lua) 763 | if func == nil then 764 | return nil 765 | end 766 | return func() 767 | end 768 | 769 | -- 合并路径与文件名 770 | function getFilePath(filename,path) 771 | if filename == nil or filename == "" then 772 | error("文件名不能为空或nil") 773 | end 774 | if path == nil then 775 | error("文件路径不能为nil") 776 | end 777 | local filepath = "" 778 | if string.sub(path, -2) == "\\" then 779 | filepath = "\\" 780 | end 781 | filepath = tostring(path) .. filepath .. tostring(filename) 782 | return filepath 783 | end 784 | -- 读取一个简单配置文件(xxx.ini) 785 | function tool_readini(filename,path) 786 | if path == nil or path == "" then path = aeg.path.config end 787 | local filepath = getFilePath(filename,path) 788 | res = {} 789 | file = io.open(filepath, "r") 790 | if not io.type(file) then 791 | error("[读取]简单配置文件 "..filepath.." 无法打开!") 792 | end 793 | line = file:read() 794 | if line ~= "[default]" then 795 | file:close() 796 | error("[读取]简单配置文件 "..filepath.." 内容不合法!") 797 | end 798 | line = file:read() 799 | while line do 800 | pos = string.find(line,"=") 801 | if pos == nil then 802 | file:close() 803 | error("[读取]简单配置文件 "..filepath.." 内容不合法!") 804 | end 805 | key = string.sub(line,0,pos - 1) 806 | val = string.sub(line,pos + 1,-1) 807 | val = string.gsub(val,"\\n","\n") 808 | val = string.gsub(val,"\\\\","\\") 809 | res[key] = val 810 | 811 | line = file:read() 812 | end 813 | -- 关闭打开的文件 814 | file:close() 815 | return res 816 | end 817 | -- 写入一个简单配置文件(只支持字符串或数值为键,布尔、字符串或数值为值。) 818 | function tool_writeini(tabval,filename,path) 819 | if path == nil or path == "" then path = aeg.path.config end 820 | local filepath = getFilePath(filename,path) 821 | if type(tabval) ~= "table" then 822 | error("[写入]写入配置文件时错误!写入类型异常!错误文件->"..filepath) 823 | end 824 | file = io.open(filepath, "w+") 825 | if not io.type(file) then 826 | file:close() 827 | error("[写入]简单配置文件 "..filepath.." 无法打开!") 828 | end 829 | file:write("[default]\n") 830 | for k,v in pairs(tabval) do 831 | v = tostring(v) 832 | v = string.gsub(v,"\\","\\\\") 833 | v = string.gsub(v,"\n","\\n") 834 | v = v.."\n" 835 | file:write(k.."="..v) 836 | end 837 | -- 关闭打开的文件 838 | file:close() 839 | end 840 | 841 | -- 读配置 参数为签名 842 | function tool_readConfig(signature) 843 | if signature == nil then 844 | if _G.script_signature == nil then 845 | error("读写配置的签名不能为空!") 846 | else 847 | signature = _G.script_signature 848 | end 849 | end 850 | local res = {} 851 | local status = xpcall( 852 | function () 853 | res = tool_readini(signature..".ini") 854 | end 855 | , 856 | function (err) 857 | levelout(4,err) 858 | levelout(4,"\n") 859 | end 860 | ) 861 | return res 862 | end 863 | -- 写配置 参数为要写入的内容,签名 864 | function tool_saveConfig(conftab,signature) 865 | if signature == nil then 866 | if _G.script_signature == nil then 867 | error("读写配置的签名不能为空!") 868 | else 869 | signature = _G.script_signature 870 | end 871 | end 872 | if conftab == nil then 873 | error("保存的配置不能为空!") 874 | end 875 | if close_configwrite then 876 | levelout(4,"配置文件写入已关闭") 877 | return 878 | end 879 | local res = {} 880 | local status = xpcall( 881 | function () 882 | res = tool_writeini(conftab,signature..".ini") 883 | end 884 | , 885 | function (err) 886 | levelout(4,err) 887 | levelout(4,"\n") 888 | end 889 | ) 890 | end 891 | -- 读指定配置 参数为 键名,签名 892 | function tool_getConfig(key,signature) 893 | return tool_readConfig(signature)[key] 894 | end 895 | -- 写指定配置 参数为 键名,值,签名 896 | function tool_setConfig(key,val,signature) 897 | res = tool_readConfig(signature) 898 | res[key] = val 899 | tool_saveConfig(res,signature) 900 | end 901 | -- 清空所有配置 参数为 签名 902 | function tool_clearConfig(signature) 903 | tool_saveConfig({},signature) 904 | end 905 | -- 创建绑定配置的菜单(需要定义脚本签名才可使用) 参数:菜单前缀,菜单名,绑定的表-键只能为文本-值只能为bool 906 | function tool_MultSelectMenu(prefix,name,bindtab,des) 907 | local res = tool_readConfig() 908 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 909 | prefix = prefix.."/" 910 | end 911 | if string.sub(name, -2) == "/" then 912 | name = string.sub(name, 0, -2) 913 | end 914 | if des == nil then 915 | des = "" 916 | end 917 | local getFk = function (name,k) 918 | return "verif_"..name.."_"..k 919 | end 920 | local menu = { 921 | rf = function(prefix,name,bindtab,key) 922 | bindtab[key] = not bindtab[key] 923 | tool_setConfig(getFk(name,key),bindtab[key]) 924 | end 925 | ,af = function(prefix,name,bindtab,key) 926 | return bindtab[key] 927 | end 928 | } 929 | 930 | local bind_metatable = { 931 | __index = function(mytable, key) 932 | if key == "set" then 933 | return functool_partial(menu.rf,prefix,name,mytable) 934 | elseif key == "get" then 935 | return functool_partial(menu.af,prefix,name,mytable) 936 | end 937 | end 938 | } 939 | setmetatable(bindtab,bind_metatable) 940 | for k,v in pairs(bindtab) do 941 | if type(k) ~= "string" then 942 | error("错误的绑定表",2) 943 | end 944 | if type(v) ~= "boolean" then 945 | error("错误的绑定表值,绑定的值只能为boolean类型",2) 946 | end 947 | if res[getFk(name,k)] == nil then 948 | res[getFk(name,k)] = v 949 | else 950 | bindtab[k] = (res[getFk(name,k)] == "true") 951 | end 952 | aeg.regMacro( 953 | prefix..name.."/"..k 954 | ,name.."-"..des 955 | ,functool_partial(menu.rf,prefix,name,bindtab,k) 956 | ,nil 957 | ,functool_partial(menu.af,prefix,name,bindtab,k) 958 | ) 959 | end 960 | 961 | end 962 | 963 | -- 创建绑定配置的单选菜单(多选一) 参数:菜单前缀,菜单名,绑定的数组-只能为数组-键now标识当前选择 964 | function tool_SingleSelectMenu(prefix,name,bindtab,des) 965 | local res = tool_readConfig() 966 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 967 | prefix = prefix.."/" 968 | end 969 | if string.sub(name, -2) == "/" then 970 | name = string.sub(name, 0, -2) 971 | end 972 | if des == nil then 973 | des = "" 974 | end 975 | local getFk = function (name,k) 976 | return "verif_"..name.."_"..k 977 | end 978 | local menu = { 979 | rf = function(prefix,name,bindtab,key) 980 | bindtab["now"] = key 981 | tool_setConfig(getFk(name,"now"),key) 982 | end 983 | ,af = function(prefix,name,bindtab,key) 984 | return bindtab["now"] == key 985 | end 986 | } 987 | local bind_metatable = { 988 | __index = function(mytable, key) 989 | if key == "set" then 990 | return functool_partial(menu.rf,prefix,name,mytable) 991 | elseif key == "get" then 992 | return functool_partial(menu.af,prefix,name,mytable) 993 | end 994 | end 995 | } 996 | setmetatable(bindtab,bind_metatable) 997 | for k,v in pairs(bindtab) do 998 | if type(k) ~= "string" then 999 | error("错误的绑定表,单选菜单键只能为字符串或now",2) 1000 | end 1001 | if k == "now" and type(v) ~= "string" then 1002 | error("错误的绑定表,now键对应的值只能为字符串",2) 1003 | end 1004 | if k == "now" then 1005 | if res[getFk(name,k)] then 1006 | bindtab["now"] = res[getFk(name,k)] 1007 | end 1008 | goto forend 1009 | end 1010 | aeg.regMacro( 1011 | prefix..name.."/"..k 1012 | ,name.."-"..des 1013 | ,functool_partial(menu.rf,prefix,name,bindtab,k) 1014 | ,nil 1015 | ,functool_partial(menu.af,prefix,name,bindtab,k) 1016 | ) 1017 | ::forend:: 1018 | end 1019 | 1020 | end 1021 | 1022 | -- 创建菜单类 1023 | function tool_next(name,nexttable,defaultmenu) 1024 | -- 创建下级菜单 1025 | return { 1026 | class = "next" 1027 | ,name = name 1028 | ,nexttable = nexttable 1029 | ,default = defaultmenu 1030 | } 1031 | end 1032 | 1033 | function tool_About() 1034 | -- 关于的显示函数 1035 | local function about() 1036 | msg = '' 1037 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n" 1038 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n" 1039 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n".."\n" 1040 | msg = msg..'----更新日志结束----'.."\n" 1041 | msg = msg..tostring(_G.script_ChangeLog) .. "\n" 1042 | msg = msg..'-↑↑-更新日志-↑↑-'.."\n" 1043 | msg = msg..'----'..tostring(_G.script_name)..'----'.."\n" 1044 | msg = msg..'作者:'..tostring(_G.script_author).."\n" 1045 | msg = msg..'版本:'..tostring(_G.script_version).."\n" 1046 | msg = msg..'描述:'..tostring(_G.script_description).."\n" 1047 | msg = msg..'\n>>>>关于<<<<'.."\n"..tostring(_G.script_about).."\n" 1048 | aegisub.debug.out(0, msg.."\n") 1049 | end 1050 | return { 1051 | class = "menu" 1052 | ,name = "关于" 1053 | ,des = "显示关于" 1054 | ,processing = about 1055 | ,validation = nil 1056 | ,is_active = nil 1057 | } 1058 | end 1059 | function tool_Menu(name,des,processing,validation,is_active) 1060 | if name == nil or type(name) ~= "string" then 1061 | error("菜单的名称必须是个有效的文本",2) 1062 | end 1063 | if processing == nil then 1064 | error("菜单的应用函数必须存在",2) 1065 | end 1066 | return { 1067 | class = "menu" 1068 | ,name = name 1069 | ,des = des 1070 | ,processing = processing 1071 | ,validation = validation 1072 | ,is_active = is_active 1073 | } 1074 | end 1075 | function tool_CheckBoxMenu(name,bind,des) 1076 | if type(bind) ~= "table" then 1077 | error("绑定的对象必须是表格",2) 1078 | end 1079 | return { 1080 | class = "checkbox" 1081 | ,name = name 1082 | ,des = des 1083 | ,bind = bind 1084 | } 1085 | end 1086 | function tool_RadioMenu(name,bind,des) 1087 | if type(bind) ~= "table" or bind.now == nil then 1088 | error("绑定的对象必须是表格,且必须拥有now键",2) 1089 | end 1090 | return { 1091 | class = "radio" 1092 | ,name = name 1093 | ,des = des 1094 | ,bind = bind 1095 | } 1096 | end 1097 | 1098 | -- 应用菜单类 1099 | function tool_applyMenu(prefix,name,menu) 1100 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 1101 | prefix = prefix.."/" 1102 | end 1103 | if string.sub(name, -2) == "/" then 1104 | name = string.sub(name, 0, -2) 1105 | end 1106 | if menu.des == nil then 1107 | menu.des = name 1108 | end 1109 | if menu.class == "menu" then 1110 | aeg.regMacro( 1111 | prefix..name.."/"..menu.name 1112 | ,menu.des 1113 | ,menu.processing 1114 | ,menu.validation 1115 | ,menu.is_active 1116 | ) 1117 | elseif menu.class == "checkbox" then 1118 | tool_MultSelectMenu(prefix..name,menu.name,menu.bind,menu.des) 1119 | elseif menu.class == "radio" then 1120 | tool_SingleSelectMenu(prefix..name,menu.name,menu.bind,menu.des) 1121 | else 1122 | error("未知的菜单类型",2) 1123 | end 1124 | 1125 | end 1126 | 1127 | -- 自动创建菜单 1128 | --[[ 1129 | 使用符合规则的表格可自动创建 1130 | ]] 1131 | function tool_AutoMenu(name,menutable) 1132 | if type(menutable) ~= "table" then 1133 | error("创建菜单时菜单必须为合法表",2) 1134 | end 1135 | local function getNext(prefix,name,t) 1136 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 1137 | prefix = prefix.."/" 1138 | end 1139 | if string.sub(name, -2) == "/" then 1140 | name = string.sub(name, 0, -2) 1141 | end 1142 | for _,v in pairs(t) do 1143 | if type(v) ~= "table" or v.class == nil then 1144 | error("表结构不合法,菜单的值必须为表,且是合法菜单项",2) 1145 | elseif v.class == "next" then 1146 | if v.default then 1147 | if v.default.class == "menu" then 1148 | v.default.name = name 1149 | tool_applyMenu(prefix,name,v.default) 1150 | else 1151 | error("表结构不合法,默认菜单的值必须为合法menu类") 1152 | end 1153 | end 1154 | getNext(prefix..name,v.name,v.nexttable) 1155 | else 1156 | tool_applyMenu(prefix,name,v) 1157 | end 1158 | end 1159 | end 1160 | getNext("",name,menutable) 1161 | end 1162 | 1163 | -- 工具表 1164 | local tools = { 1165 | serialize = tool_serialize -- 序列化 1166 | ,unserialize = tool_unserialize -- 反序列化 1167 | ,valueToStr = valueToStr -- 将值转换为字符串 1168 | ,readIni = tool_readini -- 读取一个ini文件 1169 | ,writeIni = tool_writeini -- 写入一个ini文件 1170 | ,readConfig = tool_readConfig -- 读取插件配置 1171 | ,saveConfig = tool_saveConfig -- 更新插件配置 1172 | ,getConfig = tool_getConfig -- 读指定配置 1173 | ,setConfig = tool_setConfig -- 写指定配置 1174 | ,clearConfig = tool_clearConfig -- 清空配置 1175 | ,func_partial = functool_partial -- 创建偏函数 1176 | ,checkboxMenu = tool_MultSelectMenu -- 快速创建多选菜单(对勾菜单) 1177 | ,radioMenu = tool_SingleSelectMenu -- 快速创建单选菜单(多选一) 1178 | ,automenu = tool_AutoMenu 1179 | ,applymenu = tool_applyMenu 1180 | ,menus = { 1181 | next = tool_next 1182 | ,menu = tool_Menu 1183 | ,checkbox = tool_CheckBoxMenu 1184 | ,radio = tool_RadioMenu 1185 | ,about = tool_About 1186 | } 1187 | } 1188 | 1189 | -- table表的copy函数(来自Yutils) 1190 | function table.copy(t, depth) 1191 | -- Check argument 1192 | if type(t) ~= "table" or depth ~= nil and not(type(depth) == "number" and depth >= 1) then 1193 | error("table and optional depth expected", 2) 1194 | end 1195 | -- Copy & return 1196 | local function copy_recursive(old_t) 1197 | local new_t = {} 1198 | for key, value in pairs(old_t) do 1199 | new_t[key] = type(value) == "table" and copy_recursive(value) or value 1200 | end 1201 | return new_t 1202 | end 1203 | local function copy_recursive_n(old_t, depth) 1204 | local new_t = {} 1205 | for key, value in pairs(old_t) do 1206 | new_t[key] = type(value) == "table" and depth >= 2 and copy_recursive_n(value, depth-1) or value 1207 | end 1208 | return new_t 1209 | end 1210 | return depth and copy_recursive_n(t, depth) or copy_recursive(t) 1211 | end 1212 | 1213 | 1214 | -- 暴露的接口 1215 | --[[ 1216 | -- 此注释里的语句可将环境导入全局环境中 1217 | -- 导入 1218 | local cx_help = require"CX_AEG插件辅助函数库" 1219 | -- 合并函数环境 1220 | cx_help.table.merge(_G,cx_help) 1221 | ]] 1222 | local CX_AEG_IMP = { 1223 | -- 设置(设置默认输出等级) 1224 | setting = setting 1225 | -- AEG简写定义 1226 | ,aeg = aeg 1227 | 1228 | -- table扩展(支持表合并、表复制) 1229 | ,table = table 1230 | ,string = string 1231 | 1232 | -- 默认简化输出(支持多参数且支持nil输出) 1233 | ,print = print 1234 | ,println = println 1235 | 1236 | -- 完整输出(支持多参数,支持table输出) 1237 | ,vprint = var_print 1238 | ,vprintln = var_println 1239 | ,DD = var_println 1240 | 1241 | -- 其他环境扩展 1242 | ,randomseed = math.randomseed 1243 | ,random = math.random 1244 | 1245 | -- 简单的弹出窗口(仿JavaScript设计) 1246 | ,alert = alert 1247 | ,confirm = confirm 1248 | ,inputTextArea = inputTextArea 1249 | ,prompt = prompt 1250 | ,input = input 1251 | ,inputInt = inputInt 1252 | ,inputFloat = inputFloat 1253 | 1254 | 1255 | -- 定义退出 1256 | ,exit = aegisub.cancel 1257 | ,tools = tools -- 工具集 1258 | ,display = display -- 界面辅助 1259 | ,subsTool = subsTool -- AEG字幕行工具 1260 | ,menus = tools.menus -- 菜单快捷定义 1261 | } 1262 | -- 置入随机数种子 1263 | math.randomseed(os.time()) 1264 | 1265 | 1266 | -- 暴露的接口 1267 | return CX_AEG_IMP 1268 | 1269 | 1270 | 1271 | -------------------------------------------------------------------------------- /AEG插件快速装载器/更新简述.md: -------------------------------------------------------------------------------- 1 | 暂无 2 | -------------------------------------------------------------------------------- /ASS WE CAN-已发布版本存档/ASS WE CANv1.1.1 正式版[2019年1月29日]-包含安装与使用说明 需解压.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/ASS WE CAN-已发布版本存档/ASS WE CANv1.1.1 正式版[2019年1月29日]-包含安装与使用说明 需解压.zip -------------------------------------------------------------------------------- /ASS WE CAN-已发布版本存档/ASS WE CAN安装及使用方法.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/ASS WE CAN-已发布版本存档/ASS WE CAN安装及使用方法.pdf -------------------------------------------------------------------------------- /ASS WE CAN-已发布版本存档/README.md: -------------------------------------------------------------------------------- 1 | # ASS WE CAN 2 | 主要功能:歌轴辅助、快速移轴、对照填充 3 | -------------------------------------------------------------------------------- /CX_AEG插件辅助函数库/README.md: -------------------------------------------------------------------------------- 1 | # CX_AEG插件辅助函数库 2 | 3 | 用于辅助插件编写的函数库,详情可参照"CXkara_辅助函数库示例及测试" 4 | 5 | 目前还处于开发的初级阶段。 6 | 7 | 8 | 9 | v1.0.1dev 关联的插件有: 10 | 11 | AEG插件快速装载器、复查助手 -------------------------------------------------------------------------------- /CX_AEG插件辅助函数库/[v1.0.1dev]CX_AEG插件辅助函数库.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/CX_AEG插件辅助函数库/[v1.0.1dev]CX_AEG插件辅助函数库.zip -------------------------------------------------------------------------------- /CX_AEG插件辅助函数库/当前版本/CX_AEG插件辅助函数库.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | AEG插件辅助函数库 3 | 版权所属:晨轩°(QQ3309003591) 4 | 接口文档 5 | ]] 6 | -- 模块基础信息、接口及常量定义 7 | -- 版本号 8 | lib_version = "1.0.1dev" 9 | -- 更新日志 10 | lib_ChangeLog = [[ 11 | 1.0.1dev 12 | 支持了多选菜单的双向绑定 13 | ]] 14 | 15 | local lfs = require "lfs" 16 | local re = require 'aegisub.re' 17 | local LEVEL = { 18 | FATAL = 0 19 | ,ERROR = 1 20 | ,WARNING = 2 21 | ,INFO = 3 -- AEG默认等级 22 | ,DEBUG = 4 23 | ,TRACK = 5 24 | } 25 | local print_level = LEVEL.INFO -- 默认输出等级,用于输出文本 26 | local close_configwrite = false -- 关闭配置文件写入(通过本函数库执行的写入都将被禁止) 27 | -- 设置接口 28 | function setLevel(level) 29 | if type(level) ~= "number" and ( level >= 1 and level <= 5 ) then 30 | error("设置的等级必须为整数,且在1~5之间。") 31 | end 32 | print_level = math.modf(level) 33 | end 34 | function getLevel() 35 | return print_level 36 | end 37 | function setCloseConfigWrite(b) 38 | close_configwrite = (b == true) 39 | end 40 | function getCloseConfigWrite() 41 | return close_configwrite 42 | end 43 | 44 | -- 设置表 45 | local setting = { 46 | LEVEL = LEVEL 47 | ,setLevel = setLevel -- 设置默认输出等级 48 | ,getLevel = getLevel 49 | ,setCloseConfigWrite = setCloseConfigWrite -- 设置关闭配置写入 50 | ,getCloseConfigWrite = getCloseConfigWrite 51 | } 52 | -- AEG兼容定义(用于使插件在AEG中拥有与IDE相同的响应) 53 | -- 等级输出 54 | function levelout(level,vaule) 55 | aegisub.debug.out(level, vaule) 56 | end 57 | -- 标准输出 58 | function standout(vaule) 59 | aegisub.debug.out(print_level, vaule) 60 | end 61 | -- 打印所有能打印的类型 62 | function valueToStr(value) 63 | if value == nil then 64 | return "nil" 65 | end 66 | if type(value) ~= "table" then 67 | return tostring(value) 68 | end 69 | res = "" 70 | -- print覆盖 71 | local function print(val) 72 | if val == nil then 73 | res = res .. 'nil'..'\n' 74 | else 75 | res = res .. tostring(val)..'\n' 76 | end 77 | end 78 | -- 好用的table输出函数 79 | local function print_r(t) 80 | local print_r_cache={} 81 | local function sub_print_r(t,indent) 82 | if (print_r_cache[tostring(t)]) then 83 | print(indent.."*"..tostring(t)) 84 | else 85 | print_r_cache[tostring(t)]=true 86 | if (type(t)=="table") then 87 | for pos,val in pairs(t) do 88 | pos = tostring(pos) 89 | if (type(val)=="table") then 90 | print(indent.."["..pos.."] => "..tostring(t).." {") 91 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 92 | print(indent..string.rep(" ",string.len(pos)+6).."}") 93 | elseif (type(val)=="string") then 94 | print(indent.."["..pos..'] => "'..val..'"') 95 | else 96 | print(indent.."["..pos.."] => "..tostring(val)) 97 | end 98 | end 99 | else 100 | print(indent..tostring(t)) 101 | end 102 | end 103 | end 104 | if (type(t)=="table") then 105 | print(tostring(t).." {") 106 | sub_print_r(t," ") 107 | print("}") 108 | else 109 | sub_print_r(t," ") 110 | end 111 | end 112 | -- 运行函数 113 | print_r(value) 114 | -- 打印结果 115 | return string.sub(res,0,-2) 116 | end 117 | 118 | -- 短输出 119 | function print(...) 120 | local arg = {...} 121 | maxi = 1 122 | for i,v in pairs(arg) do 123 | if i > maxi then maxi = i end 124 | end 125 | printResult = "" 126 | for i = 1,maxi do 127 | printResult = printResult .. valueToStr(arg[i]) 128 | end 129 | standout(printResult) 130 | end 131 | function println(...) 132 | local arg = {...} 133 | maxi = 1 134 | for i,v in pairs(arg) do 135 | if i > maxi then maxi = i end 136 | end 137 | arg[maxi + 1] = "\n" 138 | print(unpack(arg)) 139 | end 140 | -- 完整输出 141 | function var_print(...) 142 | local arg = {...} 143 | maxi = 1 144 | for i,v in pairs(arg) do 145 | if i > maxi then maxi = i end 146 | end 147 | printResult = "" 148 | for i = 1,maxi do 149 | printResult = printResult .. valueToStr(arg[i]) 150 | end 151 | standout(printResult) 152 | end 153 | function var_println(...) 154 | local arg = {...} 155 | maxi = 1 156 | for i,v in pairs(arg) do 157 | if i > maxi then maxi = i end 158 | end 159 | arg[maxi + 1] = "\n" 160 | var_print(unpack(arg)) 161 | end 162 | 163 | --[[ 其它工具集 ]] 164 | -- 创建偏函数 165 | function functool_partial(func,...) 166 | local arg = {...} 167 | local getMerge = function(arg2) 168 | res = {} 169 | for _,v in pairs(arg) do 170 | table.insert(res,v) 171 | end 172 | for _,v in pairs(arg2) do 173 | table.insert(res,v) 174 | end 175 | return res 176 | end 177 | return function(...) 178 | local arg2 = {...} 179 | return func(unpack(getMerge(arg2))) 180 | end 181 | end 182 | 183 | 184 | --[[ 185 | 扩展环境 186 | ]] 187 | -- 合并表 188 | function table.merge(source,addtable) 189 | for k,v in pairs(addtable) do 190 | source[k] = v 191 | end 192 | return source 193 | end 194 | -- 去除首尾空格 195 | local str_trim_expr_pre = re.compile([[^\s+]],re.NOSUB,re.NO_MOD_M) 196 | local str_trim_expr_end = re.compile([[\s+$]],re.NOSUB,re.NO_MOD_M) 197 | function string.trim(str) 198 | out_str, rep_count = str_trim_expr_pre:sub(str,'') 199 | out_str, rep_count1 = str_trim_expr_end:sub(out_str,'') 200 | if not rep_count then rep_count = 0 end 201 | if not rep_count1 then rep_count1 = 0 end 202 | rep_count = rep_count + rep_count1 203 | return out_str 204 | end 205 | 206 | 207 | --[[ 208 | 缩写集 209 | ]] 210 | 211 | 212 | -- 缩写aegisub表 213 | local aeg = aegisub 214 | local aegmin = {} 215 | local aegPath = {} 216 | aeg.regMacro = aegisub.register_macro 217 | aeg.regFilter = aegisub.register_filter 218 | aeg.levelout = levelout 219 | aeg.standout = standout 220 | aeg.exit = aegisub.cancel 221 | aeg.path = aegPath 222 | 223 | setmetatable(aeg, { 224 | __index = function(t, key) 225 | if aegisub.progress ~= nil then 226 | aegmin.setTitle = aegisub.progress.title 227 | aegmin.setProgress = aegisub.progress.set 228 | aegmin.setTask = aegisub.progress.task 229 | aegmin.cancelled = aegisub.progress.is_cancelled 230 | -- 设置还原点 231 | aegmin.setUndo = aegisub.set_undo_point 232 | -- 等待AEG响应,同时判断是否取消执行,用户取消执行将结束脚本,参数为结束运行钩子函数 233 | aegmin.waitAeg = function (func) 234 | if aegisub.progress.is_cancelled() then 235 | if type(func) == "function" then 236 | func() 237 | end 238 | end 239 | end 240 | end 241 | return aegmin[key] 242 | end 243 | }) 244 | -- 获取路径说明,无法获取时返回nil 245 | setmetatable(aegPath, { 246 | __index = function(t, key) 247 | if key == "data" then 248 | -- 存储应用数据的位置。在Windows下是指Aegisub安装目录(.exe的位置)。在Mac OS X下被包含在应用包里。在其他类POSIX系统下的目录为 $prefix/share/aegisub/. 249 | return aegisub.decode_path('?data\\') 250 | 251 | elseif key == "user" then 252 | -- 存储用户数据的位置,例如配置文件,自动备份文件和其他附加的东西。在Windows下,这个路径是 %APPDATA%\Aegisub\; 在Mac OS X下,这个路径是 $HOME/Library/ApplicationSupport/Aegisub/; 在其他类OSIX系统下,这个路径是 $HOME/.aegisub/; 在便携版Aegisub中这个目录是 ?data。 253 | return aegisub.decode_path('?user\\') 254 | 255 | elseif key == "temp" then 256 | -- 系统临时文件目录。音频缓存和临时字幕文件都存储在这个位置。 257 | return aegisub.decode_path('?temp') 258 | 259 | elseif key == "local" or key == "cache" then 260 | -- 本地用户设置路径。存储运行缓存文件的位置,例如FFMS2索引和字体配置缓存。Windows下为 %LOCALAPPDATA%\Aegisub 其他系统是 ?user。 261 | return aegisub.decode_path('?local\\') 262 | 263 | elseif key == "script" then 264 | -- 只有当你打开一个已经保存在本地的字幕文件时才有作用,为该字幕文件的保存的位置。 265 | text = aegisub.decode_path('?script\\') 266 | if text == '?script\\' then text = nil end 267 | return text 268 | 269 | elseif key == "video" then 270 | -- 只有读取本地视频后才有作用,为当前读取视频文件的路径,注意读取空白视频时是无法使用该路径的。 271 | text = aegisub.decode_path('?video\\') 272 | if text == '?video\\' then text = nil end 273 | return text 274 | 275 | elseif key == "audio" then 276 | -- 只有读取本地音频后才有作用,为当前读取音频文件的路径,注意读取空白音频时是无法使用该路径的。 277 | text = aegisub.decode_path('?audio\\') 278 | if text == '?audio\\' then text = nil end 279 | return text 280 | -- 扩展路径 281 | elseif key == "config" then 282 | -- 配置文件路径 283 | text = aegisub.decode_path('?user\\').."script_config\\" 284 | lfs.mkdir(text) 285 | return text 286 | 287 | elseif key == "global_script" then 288 | -- 自动化脚本文件路径 289 | text = aegisub.decode_path('?data\\').."automation\\autoload\\" 290 | return text 291 | 292 | elseif key == "global_lib" then 293 | -- 库文件路径 294 | text = aegisub.decode_path('?data\\').."automation\\include\\" 295 | return text 296 | 297 | elseif key == "autosave" then 298 | -- 自动保存路径 299 | text = aegisub.decode_path('?user\\').."autosave\\" 300 | return text 301 | 302 | elseif key == "autoback" then 303 | -- 自动备份路径 304 | text = aegisub.decode_path('?user\\').."autoback\\" 305 | return text 306 | 307 | end 308 | return nil 309 | end 310 | }) 311 | 312 | -- 函数定义 313 | --[[ 314 | 显示集 315 | ]] 316 | 317 | -- 显示一个简单的窗口,参数(提示信息[,类型标识[,默认值]]) 318 | --[[ 319 | 类型标识(返回类型): 320 | 0-提示框(nil),提示需要每行尽可能短(不超过9个字) 321 | 1-确认取消框(bool) 322 | 2-单行文本输入框(string or nil) 323 | 3-单行整数输入框(number or nil) 324 | 4-单行小数输入框(number or nil) 325 | 注意:整数与小数输入有误时不会限制或报错,可能得到奇怪的结果。 326 | ]] 327 | function QuickWindow(msg,type_num,default) 328 | local config = {} -- 窗口配置 329 | local result = nil -- 返回结果 330 | local buttons = nil 331 | local button_ids = nil 332 | if type(msg) ~= 'string' then 333 | error('display.confirm参数错误-1,提示信息必须存在且为文本类型!',2) 334 | end 335 | if type_num == nil then type_num = 0 end 336 | if type(type_num) ~= 'number' then 337 | -- db.var_export(type_num) 338 | error('display.confirm参数错误-2,类型标识必须是数值!',2) 339 | end 340 | 341 | if type_num == 0 then 342 | config = { 343 | {class="label", label=msg, x=0, y=0,width=8} 344 | } 345 | buttons = {aegisub.gettext'Yes'} 346 | button_ids = {ok = aegisub.gettext'Yes'} 347 | elseif type_num == 1 then 348 | config = { 349 | {class="label", label=msg, x=0, y=0,width=12} 350 | } 351 | elseif type_num == 2 then 352 | if default == nil then default = '' end 353 | config = { 354 | {class="label", label=msg, x=0, y=0,width=12}, 355 | {class="edit", name='text',text=tostring(default), x=0, y=1,width=12} 356 | } 357 | elseif type_num == 3 then 358 | if default == nil then default = 0 end 359 | if type(type_num) ~= 'number' then 360 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 361 | end 362 | config = { 363 | {class="label", label=msg, x=0, y=0,width=12}, 364 | {class="intedit", name='int',value=default, x=0, y=1,width=12} 365 | } 366 | elseif type_num == 4 then 367 | if default == nil then default = 0 end 368 | if type(type_num) ~= 'number' then 369 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 370 | end 371 | config = { 372 | {class="label", label=msg, x=0, y=0,width=12}, 373 | {class="floatedit", name='float',value=default, x=0, y=1,width=12} 374 | } 375 | elseif type_num == 5 then 376 | if default == nil then default = '' end 377 | if type(type_num) ~= 'number' then 378 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 379 | end 380 | config = { 381 | {class="label", label=msg, x=0, y=0,width=12}, 382 | {class="textbox", name='textbox',text=tostring(default), x=0, y=1,width=15,height=10} 383 | } 384 | else 385 | error('display.confirm参数错误,无效的类型标识!',2) 386 | end 387 | 388 | -- 显示对话框 389 | btn, btnresult = aegisub.dialog.display(config,buttons,button_ids) 390 | 391 | -- 处理并返回结果 392 | if type_num == 0 then 393 | result = nil 394 | elseif type_num == 1 then 395 | if btn ~= false then 396 | result = true 397 | else 398 | result = false 399 | end 400 | elseif type_num == 2 then 401 | if btn ~= false then 402 | result = btnresult.text 403 | end 404 | elseif type_num == 3 then 405 | if btn ~= false then 406 | result = btnresult.int 407 | end 408 | elseif type_num == 4 then 409 | if btn ~= false then 410 | result = btnresult.float 411 | end 412 | elseif type_num == 5 then 413 | if btn ~= false then 414 | result = btnresult.textbox 415 | end 416 | end 417 | -- debug.var_export(result) 418 | return result 419 | end 420 | 421 | -- JS拟态提示框 422 | -- 警告框 参数 显示的消息 | 返回值 nil 423 | function alert(msg) 424 | return QuickWindow(msg,0,"") 425 | end 426 | -- 确认框 参数 显示的消息 | 返回值 true/false 427 | function confirm(msg) 428 | return QuickWindow(msg,1,"") 429 | end 430 | -- 文本输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 431 | function prompt(msg,defval) 432 | return QuickWindow(msg,2,defval) 433 | end 434 | input = prompt 435 | 436 | -- 文本域输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 437 | function inputTextArea(msg,defval) 438 | return QuickWindow(msg,5,defval) 439 | end 440 | 441 | -- 整数输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 442 | function inputInt(msg,defval) 443 | return QuickWindow(msg,3,defval) 444 | end 445 | -- 小数输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 446 | function inputFloat(msg,defval) 447 | return QuickWindow(msg,4,defval) 448 | end 449 | 450 | 451 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 452 | select_file_last_select_path = "" 453 | function select_file(title, wildcards, default_dir, must_exist, default_file, allow_multiple) 454 | if title == nil then title = '选择文件' end 455 | if default_file == nil then default_file = '' end 456 | if default_dir == nil then 457 | default_dir = '' 458 | if select_file_last_select_path then 459 | default_dir = select_file_last_select_path 460 | else 461 | temp_path = aegisub.decode_path('?script\\file') 462 | if temp_path ~= '?script\\file' then 463 | default_dir = temp_path 464 | end 465 | end 466 | end 467 | if wildcards == nil then wildcards = '所有文件(*)|*' end 468 | if allow_multiple == nil then allow_multiple = false end 469 | if must_exist == nil then must_exist = true end 470 | file_name = aegisub.dialog.open(title, default_file, default_dir, wildcards, allow_multiple, must_exist) 471 | if file_name then select_file_last_select_path = file_name end 472 | return file_name 473 | end 474 | 475 | local display = { 476 | -- 显示一个简单的提示窗口,参数(提示信息[,类型标识[,默认值]]) 477 | QuickWindow = QuickWindow 478 | ,alert = alert -- 警告框 参数 显示的消息 | 返回值 nil 479 | ,confirm = confirm -- 确认框 参数 显示的消息 | 返回值 true/false 480 | ,inputTextArea = inputTextArea 481 | ,prompt = prompt -- 文本输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 482 | ,input = input 483 | ,inputInt = inputInt 484 | ,inputFloat = inputFloat 485 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 返回 文件路径 486 | ,select_file = select_file 487 | } 488 | 489 | --[[ 490 | 字幕行工具 491 | ]] 492 | function subs_getFirstDialogue(subs) 493 | for i = 1,#subs do 494 | if subs[i].class == 'dialogue' then 495 | return i 496 | end 497 | end 498 | end 499 | function subs_getStyles(subs) 500 | styles = {cout_n=0} -- 字幕行 501 | infos = {cout_n=0} -- 信息行 502 | unknows = {cout_n=0} -- 未知行 503 | first_dialogue = nil 504 | for i = 1,#subs do 505 | line = subs[i] 506 | if line.class == 'style' then 507 | styles.cout_n = styles.cout_n + 1 508 | styles[line.name] = { 509 | index = i 510 | ,line = line 511 | } 512 | elseif line.class == 'info' then 513 | infos.cout_n = infos.cout_n + 1 514 | infos[line.key:lower()] = { 515 | index = i 516 | ,line = line 517 | ,key = line.key 518 | ,value = line.value 519 | } 520 | elseif line.class == 'unknown' then 521 | unknows.cout_n = unknows.cout_n + 1 522 | table.insert(unknown,{ 523 | index = i 524 | ,line = line 525 | }) 526 | elseif line.class == 'dialogue' then 527 | first_dialogue = i 528 | break 529 | end 530 | end 531 | return styles,first_dialogue,infos,unknows 532 | end 533 | function subs_collect_head(subs) 534 | styles,first_dialogue,infos,unknows = subs_getStyles(subs) 535 | return { 536 | firstDialogue = first_dialogue 537 | ,styles = styles 538 | ,infos = infos 539 | ,unknows = unknows 540 | } 541 | end 542 | function subs_pre_deal(subs) 543 | --[[ 544 | 代理原subs对象 545 | 解决各种不爽的问题,尽可能降低因操作subs导致的AEG崩溃可能 546 | ]] 547 | heads = subs_collect_head(subs) 548 | local function getIter(tab,start_i,end_i) 549 | local si = start_i 550 | local ni = si 551 | local ei = end_i 552 | return function () 553 | ni = ni + 1 554 | if (ni - 1) <= ei then 555 | return ni - si,tab[ni - 1] 556 | end 557 | end 558 | end 559 | local function getCollectIter(tab) 560 | local tabiter = pairs(tab) 561 | local k = nil 562 | return function () 563 | k,v = tabiter(tab,k) 564 | if k == "cout_n" then 565 | k,v = tabiter(tab,k) 566 | end 567 | if k ~= nil then 568 | return v.index,v.line 569 | end 570 | end 571 | end 572 | proxySubs = { 573 | subs = subs 574 | ,heads = heads 575 | ,otherData = { 576 | -- 暂未确定 577 | } 578 | -- 获取原始行标 579 | ,getSourceIndex = function (i) 580 | return i + proxySubs.heads.firstDialogue - 1 581 | end 582 | -- 获取重定向行标 583 | ,getIndex = function (i) 584 | return i - proxySubs.heads.firstDialogue - 1 585 | end 586 | -- 兼容定义 587 | ,raw_insert = subs.insert -- 插入 588 | ,raw_delete = subs.delete -- 删除 589 | ,raw_deleterange = subs.deleterange -- 范围删除 590 | ,raw_append = subs.append -- 追加 591 | -- 重定向定义 592 | ,insert = function (i,...) 593 | if i < 0 then 594 | error("Out of bounds",2) 595 | end 596 | i = i + proxySubs.heads.firstDialogue - 1 597 | if i > #(proxySubs.subs) then 598 | error("Out of bounds",2) 599 | end 600 | return subs.insert(i,...) 601 | end 602 | ,delete = function (...) 603 | local args = {...} 604 | for i = 1, #args do 605 | if args[i] < 0 then 606 | error("Out of bounds",2) 607 | end 608 | args[i] = args[i] + proxySubs.heads.firstDialogue - 1 609 | if args[i] > #(proxySubs.subs) then 610 | error("Out of bounds",2) 611 | end 612 | end 613 | return subs.delete(unpack(args)) 614 | end 615 | ,deleterange = function (first,last) 616 | if first < 0 or last < 0 then 617 | error("Error delete range",2) 618 | end 619 | first = first + proxySubs.heads.firstDialogue - 1 620 | last = last + proxySubs.heads.firstDialogue - 1 621 | if first > #(proxySubs.subs) or last > #(proxySubs.subs) then 622 | error("Out of bounds",2) 623 | end 624 | 625 | if first > last then 626 | return 627 | end 628 | return subs.deleterange( 629 | first 630 | ,last 631 | ) 632 | end 633 | 634 | ,append = function (...) 635 | local args = {...} 636 | for _,v in pairs(args) do 637 | if type(v) ~= "table" or v.class ~= "dialogue" then 638 | error("插入了不合法的对话行",2) 639 | end 640 | end 641 | return subs.append(unpack(args)) 642 | end 643 | 644 | } 645 | setmetatable(proxySubs,{ 646 | __index = function (mytable, key) 647 | -- 元素数量数据 648 | if key == "n" then 649 | return #rawget(mytable,"subs") 650 | elseif key == "sn" then -- 字幕数 651 | return rawget(mytable,"heads").styles.cout_n 652 | elseif key == "in" or key == "i_n" then -- 信息行数量 653 | return rawget(mytable,"heads").infos.cout_n 654 | elseif key == "dn" then -- 对话行数量 655 | return #rawget(mytable,"subs") - rawget(mytable,"heads").firstDialogue + 1 656 | elseif key == "fd" then -- 对话行数量 657 | return rawget(mytable,"heads").firstDialogue 658 | 659 | -- 收集的表数据 660 | elseif key == "styles" then -- 样式行表 661 | return rawget(mytable,"heads").styles 662 | elseif key == "infos" then -- 信息行表 663 | return rawget(mytable,"heads").infos 664 | -- 迭代器 665 | elseif key == "InfosIter" or key == "infosIter" or key == "II" then -- 信息行表迭代器 666 | return getCollectIter(rawget(mytable,"heads").infos) 667 | elseif key == "StylesIter" or key == "stylesIter" or key == "SI" then -- 样式行表迭代器 668 | return getCollectIter(rawget(mytable,"heads").styles) 669 | elseif key == "DialogueIter" or key == "dialogueIter" or key == "DI" then -- 对话行表迭代器 670 | local tsubs = rawget(mytable,"subs") 671 | return getIter(tsubs,rawget(mytable,"heads").firstDialogue,#tsubs) 672 | end 673 | 674 | -- 对话行设置重定向 675 | if type(key) == "number" then 676 | if key <= 0 then 677 | error("Out of bounds",2) 678 | end 679 | key = key + rawget(mytable,"heads").firstDialogue - 1 680 | return rawget(mytable,"subs")[key] 681 | end 682 | return rawget(mytable,"subs")[key] 683 | end 684 | ,__newindex = function (mytable, key, value) 685 | -- 设置新的值都导向subs 686 | -- 对话行设置重定向 687 | if type(key) == "number" then 688 | if key <= 0 then 689 | error("Out of bounds",2) 690 | end 691 | if type(value) ~= "table" or value.class ~= "dialogue" then 692 | error("使用了不合法的对话行",2) 693 | end 694 | key = key + rawget(mytable,"heads").firstDialogue - 1 695 | end 696 | rawget(mytable,"subs")[key] = value 697 | if type(key) == "number" and key < heads.firstDialogue then 698 | -- 如果设置的值小于第一行对话行位置则重新收集行信息 699 | rawset(mytable,"heads",subs_collect_head(rawget(mytable,"subs"))) 700 | end 701 | end 702 | }) 703 | return proxySubs 704 | end 705 | --[[ 706 | 字幕辅助表 707 | ]] 708 | local subsTool = { 709 | -- 使用subs字幕对象 收集样式信息表 并返回样式表与首个dialogue行的下标 710 | getStyles = subs_getStyles 711 | -- 获取首个dialogue行的下标 712 | ,getFirstDialogue = subs_getFirstDialogue 713 | -- 字幕行预处理 714 | ,presubs = subs_pre_deal 715 | } 716 | 717 | 718 | 719 | --[[ 720 | 其它工具集 721 | ]] 722 | -- 序列化 723 | function tool_serialize(obj) 724 | local lua = "" 725 | local t = type(obj) 726 | if t == "number" then 727 | lua = lua .. obj 728 | elseif t == "boolean" then 729 | lua = lua .. tostring(obj) 730 | elseif t == "string" then 731 | lua = lua .. string.format("%q", obj) 732 | elseif t == "table" then 733 | lua = lua .. "{" 734 | for k, v in pairs(obj) do 735 | lua = lua .. "[" .. tool_serialize(k) .. "]=" .. tool_serialize(v) .. "," 736 | end 737 | local metatable = getmetatable(obj) 738 | if metatable ~= nil and type(metatable.__index) == "table" then 739 | for k, v in pairs(metatable.__index) do 740 | lua = lua .. "[" .. tool_serialize(k) .. "]=" .. tool_serialize(v) .. "," 741 | end 742 | end 743 | lua = lua .. "}" 744 | elseif t == "nil" then 745 | return nil 746 | else 747 | error("can not serialize a " .. t .. " type.") 748 | end 749 | return lua 750 | end 751 | -- 反序列化 752 | function tool_unserialize(lua) 753 | local t = type(lua) 754 | if t == "nil" or lua == "" then 755 | return nil 756 | elseif t == "number" or t == "string" or t == "boolean" then 757 | lua = tostring(lua) 758 | else 759 | error("can not unserialize a " .. t .. " type.") 760 | end 761 | lua = "return " .. lua 762 | local func = loadstring(lua) 763 | if func == nil then 764 | return nil 765 | end 766 | return func() 767 | end 768 | 769 | -- 合并路径与文件名 770 | function getFilePath(filename,path) 771 | if filename == nil or filename == "" then 772 | error("文件名不能为空或nil") 773 | end 774 | if path == nil then 775 | error("文件路径不能为nil") 776 | end 777 | local filepath = "" 778 | if string.sub(path, -2) == "\\" then 779 | filepath = "\\" 780 | end 781 | filepath = tostring(path) .. filepath .. tostring(filename) 782 | return filepath 783 | end 784 | -- 读取一个简单配置文件(xxx.ini) 785 | function tool_readini(filename,path) 786 | if path == nil or path == "" then path = aeg.path.config end 787 | local filepath = getFilePath(filename,path) 788 | res = {} 789 | file = io.open(filepath, "r") 790 | if not io.type(file) then 791 | error("[读取]简单配置文件 "..filepath.." 无法打开!") 792 | end 793 | line = file:read() 794 | if line ~= "[default]" then 795 | file:close() 796 | error("[读取]简单配置文件 "..filepath.." 内容不合法!") 797 | end 798 | line = file:read() 799 | while line do 800 | pos = string.find(line,"=") 801 | if pos == nil then 802 | file:close() 803 | error("[读取]简单配置文件 "..filepath.." 内容不合法!") 804 | end 805 | key = string.sub(line,0,pos - 1) 806 | val = string.sub(line,pos + 1,-1) 807 | val = string.gsub(val,"\\n","\n") 808 | val = string.gsub(val,"\\\\","\\") 809 | res[key] = val 810 | 811 | line = file:read() 812 | end 813 | -- 关闭打开的文件 814 | file:close() 815 | return res 816 | end 817 | -- 写入一个简单配置文件(只支持字符串或数值为键,布尔、字符串或数值为值。) 818 | function tool_writeini(tabval,filename,path) 819 | if path == nil or path == "" then path = aeg.path.config end 820 | local filepath = getFilePath(filename,path) 821 | if type(tabval) ~= "table" then 822 | error("[写入]写入配置文件时错误!写入类型异常!错误文件->"..filepath) 823 | end 824 | file = io.open(filepath, "w+") 825 | if not io.type(file) then 826 | file:close() 827 | error("[写入]简单配置文件 "..filepath.." 无法打开!") 828 | end 829 | file:write("[default]\n") 830 | for k,v in pairs(tabval) do 831 | v = tostring(v) 832 | v = string.gsub(v,"\\","\\\\") 833 | v = string.gsub(v,"\n","\\n") 834 | v = v.."\n" 835 | file:write(k.."="..v) 836 | end 837 | -- 关闭打开的文件 838 | file:close() 839 | end 840 | 841 | -- 读配置 参数为签名 842 | function tool_readConfig(signature) 843 | if signature == nil then 844 | if _G.script_signature == nil then 845 | error("读写配置的签名不能为空!") 846 | else 847 | signature = _G.script_signature 848 | end 849 | end 850 | local res = {} 851 | local status = xpcall( 852 | function () 853 | res = tool_readini(signature..".ini") 854 | end 855 | , 856 | function (err) 857 | levelout(4,err) 858 | levelout(4,"\n") 859 | end 860 | ) 861 | return res 862 | end 863 | -- 写配置 参数为要写入的内容,签名 864 | function tool_saveConfig(conftab,signature) 865 | if signature == nil then 866 | if _G.script_signature == nil then 867 | error("读写配置的签名不能为空!") 868 | else 869 | signature = _G.script_signature 870 | end 871 | end 872 | if conftab == nil then 873 | error("保存的配置不能为空!") 874 | end 875 | if close_configwrite then 876 | levelout(4,"配置文件写入已关闭") 877 | return 878 | end 879 | local res = {} 880 | local status = xpcall( 881 | function () 882 | res = tool_writeini(conftab,signature..".ini") 883 | end 884 | , 885 | function (err) 886 | levelout(4,err) 887 | levelout(4,"\n") 888 | end 889 | ) 890 | end 891 | -- 读指定配置 参数为 键名,签名 892 | function tool_getConfig(key,signature) 893 | return tool_readConfig(signature)[key] 894 | end 895 | -- 写指定配置 参数为 键名,值,签名 896 | function tool_setConfig(key,val,signature) 897 | res = tool_readConfig(signature) 898 | res[key] = val 899 | tool_saveConfig(res,signature) 900 | end 901 | -- 清空所有配置 参数为 签名 902 | function tool_clearConfig(signature) 903 | tool_saveConfig({},signature) 904 | end 905 | -- 创建绑定配置的菜单(需要定义脚本签名才可使用) 参数:菜单前缀,菜单名,绑定的表-键只能为文本-值只能为bool 906 | function tool_MultSelectMenu(prefix,name,bindtab,des) 907 | local res = tool_readConfig() 908 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 909 | prefix = prefix.."/" 910 | end 911 | if string.sub(name, -2) == "/" then 912 | name = string.sub(name, 0, -2) 913 | end 914 | if des == nil then 915 | des = "" 916 | end 917 | local getFk = function (name,k) 918 | return "verif_"..name.."_"..k 919 | end 920 | local menu = { 921 | rf = function(prefix,name,bindtab,key) 922 | bindtab[key] = not bindtab[key] 923 | tool_setConfig(getFk(name,key),bindtab[key]) 924 | end 925 | ,af = function(prefix,name,bindtab,key) 926 | return bindtab[key] 927 | end 928 | } 929 | 930 | local bind_metatable = { 931 | __index = function(mytable, key) 932 | if key == "set" then 933 | return functool_partial(menu.rf,prefix,name,mytable) 934 | elseif key == "get" then 935 | return functool_partial(menu.af,prefix,name,mytable) 936 | end 937 | end 938 | } 939 | setmetatable(bindtab,bind_metatable) 940 | for k,v in pairs(bindtab) do 941 | if type(k) ~= "string" then 942 | error("错误的绑定表",2) 943 | end 944 | if type(v) ~= "boolean" then 945 | error("错误的绑定表值,绑定的值只能为boolean类型",2) 946 | end 947 | if res[getFk(name,k)] == nil then 948 | res[getFk(name,k)] = v 949 | else 950 | bindtab[k] = (res[getFk(name,k)] == "true") 951 | end 952 | aeg.regMacro( 953 | prefix..name.."/"..k 954 | ,name.."-"..des 955 | ,functool_partial(menu.rf,prefix,name,bindtab,k) 956 | ,nil 957 | ,functool_partial(menu.af,prefix,name,bindtab,k) 958 | ) 959 | end 960 | 961 | end 962 | 963 | -- 创建绑定配置的单选菜单(多选一) 参数:菜单前缀,菜单名,绑定的数组-只能为数组-键now标识当前选择 964 | function tool_SingleSelectMenu(prefix,name,bindtab,des) 965 | local res = tool_readConfig() 966 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 967 | prefix = prefix.."/" 968 | end 969 | if string.sub(name, -2) == "/" then 970 | name = string.sub(name, 0, -2) 971 | end 972 | if des == nil then 973 | des = "" 974 | end 975 | local getFk = function (name,k) 976 | return "verif_"..name.."_"..k 977 | end 978 | local menu = { 979 | rf = function(prefix,name,bindtab,key) 980 | bindtab["now"] = key 981 | tool_setConfig(getFk(name,"now"),key) 982 | end 983 | ,af = function(prefix,name,bindtab,key) 984 | return bindtab["now"] == key 985 | end 986 | } 987 | local bind_metatable = { 988 | __index = function(mytable, key) 989 | if key == "set" then 990 | return functool_partial(menu.rf,prefix,name,mytable) 991 | elseif key == "get" then 992 | return functool_partial(menu.af,prefix,name,mytable) 993 | end 994 | end 995 | } 996 | setmetatable(bindtab,bind_metatable) 997 | for k,v in pairs(bindtab) do 998 | if type(k) ~= "string" then 999 | error("错误的绑定表,单选菜单键只能为字符串或now",2) 1000 | end 1001 | if k == "now" and type(v) ~= "string" then 1002 | error("错误的绑定表,now键对应的值只能为字符串",2) 1003 | end 1004 | if k == "now" then 1005 | if res[getFk(name,k)] then 1006 | bindtab["now"] = res[getFk(name,k)] 1007 | end 1008 | goto forend 1009 | end 1010 | aeg.regMacro( 1011 | prefix..name.."/"..k 1012 | ,name.."-"..des 1013 | ,functool_partial(menu.rf,prefix,name,bindtab,k) 1014 | ,nil 1015 | ,functool_partial(menu.af,prefix,name,bindtab,k) 1016 | ) 1017 | ::forend:: 1018 | end 1019 | 1020 | end 1021 | 1022 | -- 创建菜单类 1023 | function tool_next(name,nexttable,defaultmenu) 1024 | -- 创建下级菜单 1025 | return { 1026 | class = "next" 1027 | ,name = name 1028 | ,nexttable = nexttable 1029 | ,default = defaultmenu 1030 | } 1031 | end 1032 | 1033 | function tool_About() 1034 | -- 关于的显示函数 1035 | local function about() 1036 | msg = '' 1037 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n" 1038 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n" 1039 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n".."\n" 1040 | msg = msg..'----更新日志结束----'.."\n" 1041 | msg = msg..tostring(_G.script_ChangeLog) .. "\n" 1042 | msg = msg..'-↑↑-更新日志-↑↑-'.."\n" 1043 | msg = msg..'----'..tostring(_G.script_name)..'----'.."\n" 1044 | msg = msg..'作者:'..tostring(_G.script_author).."\n" 1045 | msg = msg..'版本:'..tostring(_G.script_version).."\n" 1046 | msg = msg..'描述:'..tostring(_G.script_description).."\n" 1047 | msg = msg..'\n>>>>关于<<<<'.."\n"..tostring(_G.script_about).."\n" 1048 | aegisub.debug.out(0, msg.."\n") 1049 | end 1050 | return { 1051 | class = "menu" 1052 | ,name = "关于" 1053 | ,des = "显示关于" 1054 | ,processing = about 1055 | ,validation = nil 1056 | ,is_active = nil 1057 | } 1058 | end 1059 | function tool_Menu(name,des,processing,validation,is_active) 1060 | if name == nil or type(name) ~= "string" then 1061 | error("菜单的名称必须是个有效的文本",2) 1062 | end 1063 | if processing == nil then 1064 | error("菜单的应用函数必须存在",2) 1065 | end 1066 | return { 1067 | class = "menu" 1068 | ,name = name 1069 | ,des = des 1070 | ,processing = processing 1071 | ,validation = validation 1072 | ,is_active = is_active 1073 | } 1074 | end 1075 | function tool_CheckBoxMenu(name,bind,des) 1076 | if type(bind) ~= "table" then 1077 | error("绑定的对象必须是表格",2) 1078 | end 1079 | return { 1080 | class = "checkbox" 1081 | ,name = name 1082 | ,des = des 1083 | ,bind = bind 1084 | } 1085 | end 1086 | function tool_RadioMenu(name,bind,des) 1087 | if type(bind) ~= "table" or bind.now == nil then 1088 | error("绑定的对象必须是表格,且必须拥有now键",2) 1089 | end 1090 | return { 1091 | class = "radio" 1092 | ,name = name 1093 | ,des = des 1094 | ,bind = bind 1095 | } 1096 | end 1097 | 1098 | -- 应用菜单类 1099 | function tool_applyMenu(prefix,name,menu) 1100 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 1101 | prefix = prefix.."/" 1102 | end 1103 | if string.sub(name, -2) == "/" then 1104 | name = string.sub(name, 0, -2) 1105 | end 1106 | if menu.des == nil then 1107 | menu.des = name 1108 | end 1109 | if menu.class == "menu" then 1110 | aeg.regMacro( 1111 | prefix..name.."/"..menu.name 1112 | ,menu.des 1113 | ,menu.processing 1114 | ,menu.validation 1115 | ,menu.is_active 1116 | ) 1117 | elseif menu.class == "checkbox" then 1118 | tool_MultSelectMenu(prefix..name,menu.name,menu.bind,menu.des) 1119 | elseif menu.class == "radio" then 1120 | tool_SingleSelectMenu(prefix..name,menu.name,menu.bind,menu.des) 1121 | else 1122 | error("未知的菜单类型",2) 1123 | end 1124 | 1125 | end 1126 | 1127 | -- 自动创建菜单 1128 | --[[ 1129 | 使用符合规则的表格可自动创建 1130 | ]] 1131 | function tool_AutoMenu(name,menutable) 1132 | if type(menutable) ~= "table" then 1133 | error("创建菜单时菜单必须为合法表",2) 1134 | end 1135 | local function getNext(prefix,name,t) 1136 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 1137 | prefix = prefix.."/" 1138 | end 1139 | if string.sub(name, -2) == "/" then 1140 | name = string.sub(name, 0, -2) 1141 | end 1142 | for _,v in pairs(t) do 1143 | if type(v) ~= "table" or v.class == nil then 1144 | error("表结构不合法,菜单的值必须为表,且是合法菜单项",2) 1145 | elseif v.class == "next" then 1146 | if v.default then 1147 | if v.default.class == "menu" then 1148 | v.default.name = name 1149 | tool_applyMenu(prefix,name,v.default) 1150 | else 1151 | error("表结构不合法,默认菜单的值必须为合法menu类") 1152 | end 1153 | end 1154 | getNext(prefix..name,v.name,v.nexttable) 1155 | else 1156 | tool_applyMenu(prefix,name,v) 1157 | end 1158 | end 1159 | end 1160 | getNext("",name,menutable) 1161 | end 1162 | 1163 | -- 工具表 1164 | local tools = { 1165 | serialize = tool_serialize -- 序列化 1166 | ,unserialize = tool_unserialize -- 反序列化 1167 | ,valueToStr = valueToStr -- 将值转换为字符串 1168 | ,readIni = tool_readini -- 读取一个ini文件 1169 | ,writeIni = tool_writeini -- 写入一个ini文件 1170 | ,readConfig = tool_readConfig -- 读取插件配置 1171 | ,saveConfig = tool_saveConfig -- 更新插件配置 1172 | ,getConfig = tool_getConfig -- 读指定配置 1173 | ,setConfig = tool_setConfig -- 写指定配置 1174 | ,clearConfig = tool_clearConfig -- 清空配置 1175 | ,func_partial = functool_partial -- 创建偏函数 1176 | ,checkboxMenu = tool_MultSelectMenu -- 快速创建多选菜单(对勾菜单) 1177 | ,radioMenu = tool_SingleSelectMenu -- 快速创建单选菜单(多选一) 1178 | ,automenu = tool_AutoMenu 1179 | ,applymenu = tool_applyMenu 1180 | ,menus = { 1181 | next = tool_next 1182 | ,menu = tool_Menu 1183 | ,checkbox = tool_CheckBoxMenu 1184 | ,radio = tool_RadioMenu 1185 | ,about = tool_About 1186 | } 1187 | } 1188 | 1189 | -- table表的copy函数(来自Yutils) 1190 | function table.copy(t, depth) 1191 | -- Check argument 1192 | if type(t) ~= "table" or depth ~= nil and not(type(depth) == "number" and depth >= 1) then 1193 | error("table and optional depth expected", 2) 1194 | end 1195 | -- Copy & return 1196 | local function copy_recursive(old_t) 1197 | local new_t = {} 1198 | for key, value in pairs(old_t) do 1199 | new_t[key] = type(value) == "table" and copy_recursive(value) or value 1200 | end 1201 | return new_t 1202 | end 1203 | local function copy_recursive_n(old_t, depth) 1204 | local new_t = {} 1205 | for key, value in pairs(old_t) do 1206 | new_t[key] = type(value) == "table" and depth >= 2 and copy_recursive_n(value, depth-1) or value 1207 | end 1208 | return new_t 1209 | end 1210 | return depth and copy_recursive_n(t, depth) or copy_recursive(t) 1211 | end 1212 | 1213 | 1214 | -- 暴露的接口 1215 | --[[ 1216 | -- 此注释里的语句可将环境导入全局环境中 1217 | -- 导入 1218 | local cx_help = require"CX_AEG插件辅助函数库" 1219 | -- 合并函数环境 1220 | cx_help.table.merge(_G,cx_help) 1221 | ]] 1222 | local CX_AEG_IMP = { 1223 | -- 设置(设置默认输出等级) 1224 | setting = setting 1225 | -- AEG简写定义 1226 | ,aeg = aeg 1227 | 1228 | -- table扩展(支持表合并、表复制) 1229 | ,table = table 1230 | ,string = string 1231 | 1232 | -- 默认简化输出(支持多参数且支持nil输出) 1233 | ,print = print 1234 | ,println = println 1235 | 1236 | -- 完整输出(支持多参数,支持table输出) 1237 | ,vprint = var_print 1238 | ,vprintln = var_println 1239 | ,DD = var_println 1240 | 1241 | -- 其他环境扩展 1242 | ,randomseed = math.randomseed 1243 | ,random = math.random 1244 | 1245 | -- 简单的弹出窗口(仿JavaScript设计) 1246 | ,alert = alert 1247 | ,confirm = confirm 1248 | ,inputTextArea = inputTextArea 1249 | ,prompt = prompt 1250 | ,input = input 1251 | ,inputInt = inputInt 1252 | ,inputFloat = inputFloat 1253 | 1254 | 1255 | -- 定义退出 1256 | ,exit = aegisub.cancel 1257 | ,tools = tools -- 工具集 1258 | ,display = display -- 界面辅助 1259 | ,subsTool = subsTool -- AEG字幕行工具 1260 | ,menus = tools.menus -- 菜单快捷定义 1261 | } 1262 | -- 置入随机数种子 1263 | math.randomseed(os.time()) 1264 | 1265 | 1266 | -- 暴露的接口 1267 | return CX_AEG_IMP 1268 | 1269 | 1270 | 1271 | -------------------------------------------------------------------------------- /CX_AEG插件辅助函数库/当前版本/[示例插件]CXkara_辅助函数库示例及测试.lua: -------------------------------------------------------------------------------- 1 | -- 辅助支持表 2 | local cx_help = require"CX_AEG插件辅助函数库" 3 | -- 创建合适的函数环境 4 | cx_help.table.merge(_G,cx_help) 5 | 6 | -- 脚本名 7 | script_name = "函数库示例" 8 | -- 脚本描述 9 | script_description = "用于示例函数库功能" 10 | -- 作者 11 | script_author = "晨轩°" 12 | 13 | -- CX插件扩展值 14 | -- 脚本签名(同一脚本签名请保持不变,签名不能含特殊字符,防止配置冲突) 15 | script_signature = "com.chenxuan.核心测试" 16 | -- 版本号 17 | script_version = "1.0.0dev" 18 | -- 关于 19 | script_about = [[ 20 | 这个脚本是AEG插件辅助函数库的例程 21 | ]] 22 | -- 更新日志 23 | script_ChangeLog = [[ 24 | 没有日志~ 25 | ]] 26 | 27 | 28 | -- 设置默认输出等级(不设置则默认为3) 29 | setting.setLevel(1) 30 | 31 | --[[ 32 | 辅助函数库示例函数 33 | ]] 34 | 35 | -- 快捷菜单设置例程 36 | -- 设置的值会随着用户的操作而变化(绑定表会同步这个变化,文件也会) 37 | checkbind = { 38 | ["多选A"] = true -- 多选菜单的默认值,加载后会优先读取文件中的缓存 39 | ,["多选B"] = false 40 | ,["多选C"] = true 41 | } 42 | -- tools.checkboxMenu("AEG插件辅助函数库示例","多选测试",checkbind) 43 | radiobind = { 44 | now = "第一个选择" -- 当前选择的键,会优先读取文件中的缓存 45 | ,["第一个选择"] = "选择的隐藏值,可用于标识选择" 46 | ,["第二个选择"] = 2 47 | ,["第三个选择"]= 3 48 | } 49 | -- tools.radioMenu("AEG插件辅助函数库示例","单选测试",radiobind) 50 | 51 | 52 | -- 快速定义菜单结构 53 | des = "测试菜单生成" 54 | processing = function () alert("你点击了菜单") end 55 | validation = nil 56 | is_active = nil 57 | 58 | -- 菜单结构 59 | varmenu = { 60 | menus.next( 61 | "多级菜单设置" 62 | ,{ 63 | -- 更多级菜单 64 | menus.next("再来一级" 65 | ,{ 66 | menus.menu("A",nil,processing,validation,is_active) 67 | ,menus.menu("B",nil,processing,validation,is_active) 68 | ,menus.menu("C",nil,processing) 69 | } 70 | ) 71 | ,menus.menu("普通菜单",des,processing,validation,is_active) 72 | ,menus.checkbox("多选",checkbind) 73 | ,menus.radio("单选",radiobind) 74 | ,menus.about() 75 | } 76 | -- 本体菜单 77 | ,tools.menus.menu("",des,processing,validation,is_active) 78 | ) 79 | } 80 | -- 应用菜单设置 81 | tools.automenu("AEG插件辅助函数库示例",varmenu) 82 | 83 | -- 全局环境覆盖 84 | function testDefault(subtitles, selected_lines, active_line) 85 | print("基础函数示例\n") 86 | -- 基本函数 87 | println("带LN的函数自带换行") 88 | println("以下是万能输出函数:") 89 | vprintln("字幕对象:",subtitles) 90 | DD("当前选择行列表:",selected_lines) 91 | DD("当前激活行:",active_line) 92 | println("表合并:",table.merge({a = "233"},{b = "666"})) 93 | 94 | -- 弹出窗口示例 95 | println("仿照JavaScript设计的窗口弹出") 96 | alert("这里是提醒框") 97 | b = confirm("那么,你要选择哪里呢?") 98 | if b then b = "确认" else b = "取消" end 99 | 100 | val = prompt("你选择了 "..b.." 请随意输入~") 101 | if val == nil then val = "nil -- 没有任何输入" end 102 | alert("你输入了:"..val) 103 | -- input与prompt是同一个函数 104 | -- inputInt 仅允许输入整数 105 | val = inputInt("你选择了 "..b.." 请随意输入~") 106 | if val == nil then val = "nil -- 没有任何输入" end 107 | alert("你输入了:"..tostring(val)) 108 | -- inputFloat 仅允许输入小数 109 | val = inputFloat("你选择了 "..b.." 请随意输入~") 110 | if val == nil then val = "nil -- 没有任何输入" end 111 | alert("你输入了:"..tostring(val)) 112 | 113 | 114 | exit() -- 结束脚本执行 115 | print("理论上这里不会被执行") 116 | end 117 | aeg.regMacro("AEG插件辅助函数库示例/基础函数示例","用于示例的脚本",testDefault) 118 | 119 | -- AEG简化及辅助函数 120 | function testAeg(subtitles, selected_lines, active_line) 121 | print("AEG辅助示例\n") 122 | -- 等待AEG响应,同时判断是否取消执行,用户取消执行将结束脚本,参数为结束运行钩子函数 123 | aeg.waitAeg() -- 调用aegisub.progress.is_cancelled判断 124 | -- 简化的输出函数 125 | 126 | aeg.levelout(0,"最高等级输出\n") 127 | 128 | aeg.standout("默认等级输出\n") 129 | 130 | --[[ 131 | -- 源码示例 132 | aeg.regMacro = aegisub.register_macro 133 | aeg.regFilter = aegisub.register_filter 134 | aeg.setTitle = aegisub.progress.title 135 | aeg.setProgress = aegisub.progress.set 136 | aeg.setTask = aegisub.progress.task 137 | aeg.cancelled = aegisub.progress.is_cancelled 138 | ]] 139 | -- path 140 | println("path键:") 141 | DD("配置路径:",aeg.path.config) 142 | DD("应用数据目录(AEG安装目录):",aeg.path.data) 143 | DD("用户数据目录(配置与自动备份):",aeg.path.user) 144 | DD("临时文件目录:",aeg.path.temp) 145 | DD("本地用户设置路径:",aeg.path.cache) -- 也可以使用aeg.path['local'] 146 | DD("字幕路径:",aeg.path.script) 147 | DD("视频路径:",aeg.path.video) 148 | DD("音频路径:",aeg.path.audio) 149 | -- 设置还原点 150 | line = subtitles[#subtitles] 151 | line.text = "可在 菜单->编辑 里观测到还原点名称" 152 | subtitles.append(line) 153 | aeg.setUndo("还原描述") 154 | end 155 | aeg.regMacro("AEG插件辅助函数库示例/AEG简化与辅助示例","用于示例的脚本",testAeg) 156 | 157 | -- 工具集示例 158 | function testTools(subtitles, selected_lines, active_line) 159 | print("工具集示例\n") 160 | --[[ 161 | 读写配置基于 aeg.path.config 路径 162 | 需要 配置script_signature(脚本签名) 或者 主动提供签名 才能使用 163 | 配置读写的键和值请使用纯文本,不支持文本以外的类型 164 | ]] 165 | 166 | -- 读配置 167 | ini = tools.readConfig() 168 | DD("测试读取:",ini) 169 | -- 写配置 170 | wini = {} 171 | wini.nice = "ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" 172 | wini.test = input("测试写入配置,随便写些什么吧") 173 | 174 | DD("写入内容:",wini) 175 | tools.saveConfig(wini) 176 | ini = tools.readConfig() 177 | DD("读取结果:",ini) 178 | 179 | -- 读写指定配置 180 | DD("读指定配置:","test","=",tools.getConfig("test")) 181 | tools.setConfig("test","覆盖~") 182 | DD("写指定配置:","test","=","覆盖~") 183 | DD("读指定配置:","test","=",tools.getConfig("test")) 184 | 185 | 186 | -- 序列化与反序列化(可以将table转为纯文本) 187 | ser = tools.serialize(ini) 188 | DD("序列化示例:",ser) 189 | res = tools.unserialize(ser) 190 | DD("反序列化示例:",res) 191 | end 192 | aeg.regMacro("AEG插件辅助函数库示例/工具集示例","用于示例的脚本",testTools) 193 | 194 | 195 | -- 关闭配置写入 196 | aeg.regMacro("AEG插件辅助函数库示例/关闭配置写入","用于示例的脚本" 197 | ,function() 198 | setting.setCloseConfigWrite(not setting.getCloseConfigWrite()) 199 | end 200 | ,nil 201 | ,function() 202 | return setting.getCloseConfigWrite() 203 | end 204 | ) 205 | -- 清空配置 206 | aeg.regMacro("AEG插件辅助函数库示例/清空配置示例","用于示例的脚本" 207 | ,function() tools.clearConfig() end 208 | ) 209 | 210 | 211 | -- 字幕辅助 212 | function testSubsTools(subs, selected_lines, active_line) 213 | print("字幕辅助示例\n") 214 | --[[ 215 | 字幕对象代理还原了字幕对象的操作,并重定向各类操作的起止位置 216 | 注意事项: 217 | 迭代需要使用专用迭代器 218 | ]] 219 | -- 获取字幕对象代理 220 | proxysubs = subsTool.presubs(subs) 221 | println("总行数:",proxysubs.n) 222 | println("信息行数:",proxysubs.i_n) 223 | println("样式行数:",proxysubs.sn) 224 | println("对话行数:",proxysubs.dn) 225 | DD("信息收集表:") 226 | for i,line in proxysubs.InfosIter do 227 | DD({i = i,line = line}) 228 | end 229 | DD("样式收集表:") 230 | for i,line in proxysubs.StylesIter do 231 | DD({i = i,line = line}) 232 | end 233 | DD("对话行迭代:") 234 | for i,line in proxysubs.DialogueIter do 235 | println(line.raw) 236 | end 237 | 238 | -- 设置行 239 | line = proxysubs[1] 240 | line.text = "呜呜呜" 241 | proxysubs[1] = line 242 | -- 插入行 243 | for i = 1,5 do 244 | line.text = "插入行 - "..tostring(i) 245 | proxysubs.insert(1 ,line) 246 | end 247 | line.text = "插入完成~" 248 | proxysubs.append(line) 249 | -- 删除行 250 | --[[5,X4,X3,2,X1]] 251 | proxysubs.deleterange(2,3) -- 删除 4,3 252 | proxysubs.delete(3) -- 删除 1 253 | 254 | end 255 | aeg.regMacro("AEG插件辅助函数库示例/字幕辅助示例","用于示例的脚本",testSubsTools) 256 | 257 | -- 字幕信息 258 | function testSubsTools_disonly(subs, selected_lines, active_line) 259 | print("字幕辅助示例\n") 260 | --[[ 261 | 字幕对象代理还原了字幕对象的操作,并重定向各类操作的起止位置 262 | 注意事项: 263 | 迭代需要使用专用迭代器 264 | ]] 265 | -- 获取字幕对象代理 266 | proxysubs = subsTool.presubs(subs) 267 | println("总行数:",proxysubs.n) 268 | println("信息行数:",proxysubs.i_n) 269 | println("样式行数:",proxysubs.sn) 270 | println("对话行数:",proxysubs.dn) 271 | DD("信息收集表:") 272 | for i,line in proxysubs.InfosIter do 273 | DD({i = i,line = line}) 274 | end 275 | DD("样式收集表:") 276 | for i,line in proxysubs.StylesIter do 277 | DD({i = i,line = line}) 278 | end 279 | DD("对话行迭代:") 280 | for i,line in proxysubs.DialogueIter do 281 | println(line.raw) 282 | end 283 | end 284 | aeg.regMacro("AEG插件辅助函数库示例/字幕信息收集","用于示例的脚本",testSubsTools_disonly) 285 | 286 | 287 | tools.applymenu("","AEG插件辅助函数库示例",menus.about())-- 创建关于菜单 288 | -------------------------------------------------------------------------------- /Ckara_模版应用-已发布版本存档/README.md: -------------------------------------------------------------------------------- 1 | # Ckara_模版应用 2 | 未正常开发完成的插件 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 晨轩 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua_Aegisub32 2 | Aegisub的lua插件开发仓库 3 | 4 | 目前已经开发可使用的插件 5 | 6 | [ASS WE CAN](https://github.com/chenxuan353/Lua_Aegisub32/tree/master/ASS%20WE%20CAN-%E5%B7%B2%E5%8F%91%E5%B8%83%E7%89%88%E6%9C%AC%E5%AD%98%E6%A1%A3) 7 | 8 | [复查助手](https://github.com/chenxuan353/Lua_Aegisub32/tree/master/%E5%A4%8D%E6%9F%A5%E5%8A%A9%E6%89%8B-%E5%B7%B2%E5%8F%91%E5%B8%83%E7%89%88%E6%9C%AC%E5%AD%98%E6%A1%A3) 9 | 10 | -------------------------------------------------------------------------------- /复查助手-已发布版本存档/AEG复查小助手v1.1.4 Alpha[2019年12月16日].lua: -------------------------------------------------------------------------------- 1 | local tr = aegisub.gettext 2 | 3 | script_name = tr"复查小助手" 4 | script_description = tr"检查字幕里可能存在的常见问题,并自动修复或提示,部分功能依赖Yutils" 5 | script_author = "晨轩°" 6 | script_version = "1.2.0 Alpha" 7 | 8 | -- 载入re库 正则表达 9 | local re = require 'aegisub.re' 10 | 11 | include("utils.lua") 12 | 13 | -- 辅助支持表 14 | local default_config = { 15 | debug = { 16 | level = 4 17 | } 18 | } 19 | cx_Aeg_Lua = { 20 | debug = { 21 | -- 调试输出定义 22 | -- 设置调试输出等级 23 | level = default_config.debug.level 24 | -- 修改调试输出等级(0~5 设置大于5的等级时不会输出任何信息,设置值小于0时会被置0) 25 | ,changelevel = function (level) 26 | if type(level) ~= 'number' then error('错误的调试等级设置!',2) end 27 | if level < 0 then level = 0 end 28 | cx_Aeg_Lua.debug.level = level 29 | end 30 | -- 普通输出(带换行、不带换行) 31 | ,println = function (msg) 32 | if cx_Aeg_Lua.debug.level > 5 then return end 33 | if msg ~= nil then 34 | aegisub.debug.out(cx_Aeg_Lua.debug.level, tostring(msg).."\n") 35 | else 36 | aegisub.debug.out(cx_Aeg_Lua.debug.level, "\n") 37 | end 38 | end 39 | ,print = function (msg) 40 | if cx_Aeg_Lua.debug.level > 5 then return end 41 | if msg == nil then return end 42 | aegisub.debug.out(cx_Aeg_Lua.debug.level, tostring(msg)) 43 | end 44 | 45 | -- 输出一个变量的字符串表示 46 | ,var_export = function (value) 47 | aegisub.debug.out(cx_Aeg_Lua.debug.level, '('..type(value)..')'..tostring(value)..'\n') 48 | end 49 | -- 直接输出一个表达式结构信息 50 | ,var_dump = function (value) 51 | -- print覆盖 52 | local function print(msg) 53 | cx_Aeg_Lua.debug.println(msg) 54 | end 55 | -- 好用的table输出函数 56 | local function print_r(t) 57 | local print_r_cache={} 58 | local function sub_print_r(t,indent) 59 | if (print_r_cache[tostring(t)]) then 60 | print(indent.."*"..tostring(t)) 61 | else 62 | print_r_cache[tostring(t)]=true 63 | if (type(t)=="table") then 64 | for pos,val in pairs(t) do 65 | if (type(val)=="table") then 66 | print(indent.."["..pos.."] => "..tostring(t).." {") 67 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 68 | print(indent..string.rep(" ",string.len(pos)+6).."}") 69 | elseif (type(val)=="string") then 70 | print(indent.."["..pos..'] => "'..val..'"') 71 | else 72 | print(indent.."["..pos.."] => "..tostring(val)) 73 | end 74 | end 75 | else 76 | print(indent..tostring(t)) 77 | end 78 | end 79 | end 80 | if (type(t)=="table") then 81 | print(tostring(t).." {") 82 | sub_print_r(t," ") 83 | print("}") 84 | else 85 | sub_print_r(t," ") 86 | end 87 | print() 88 | end 89 | -- 运行函数 90 | print_r(value) 91 | -- 打印结果 92 | 93 | end 94 | 95 | } 96 | ,display = { 97 | -- 显示一个简单的提示窗口,参数(提示信息[,类型标识[,默认值]]) 98 | --[[ 类型标识(返回类型): 99 | 0-提示框(nil),提示需要每行尽可能短(不超过9个字) 100 | 1-确认取消框(bool) 101 | 2-单行文本输入框(string or nil) 102 | 3-单行整数输入框(number or nil) 103 | 4-单行小数输入框(number or nil) 104 | 注意:整数与小数输入有误时不会限制或报错,可能得到奇怪的结果。 105 | ]] 106 | confirm = function (msg,type_num,default) 107 | local config = {} -- 窗口配置 108 | local result = nil -- 返回结果 109 | local buttons = nil 110 | local button_ids = nil 111 | if type(msg) ~= 'string' then 112 | error('display.confirm参数错误-1,提示信息必须存在且为文本类型!',2) 113 | end 114 | if type_num == nil then type_num = 0 end 115 | if type(type_num) ~= 'number' then 116 | -- db.var_export(type_num) 117 | error('display.confirm参数错误-2,类型标识必须是数值!',2) 118 | end 119 | 120 | if type_num == 0 then 121 | config = { 122 | {class="label", label=msg, x=0, y=0,width=8} 123 | } 124 | buttons = {'OK!'} 125 | button_ids = {ok = 'OK!'} 126 | elseif type_num == 1 then 127 | config = { 128 | {class="label", label=msg, x=0, y=0,width=12} 129 | } 130 | elseif type_num == 2 then 131 | if default == nil then default = '' end 132 | config = { 133 | {class="label", label=msg, x=0, y=0,width=12}, 134 | {class="edit", name='text',text=tostring(default), x=0, y=1,width=12} 135 | } 136 | elseif type_num == 3 then 137 | if default == nil then default = 0 end 138 | if type(type_num) ~= 'number' then 139 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 140 | end 141 | config = { 142 | {class="label", label=msg, x=0, y=0,width=12}, 143 | {class="intedit", name='int',value=default, x=0, y=1,width=12} 144 | } 145 | elseif type_num == 4 then 146 | if default == nil then default = 0 end 147 | if type(type_num) ~= 'number' then 148 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 149 | end 150 | config = { 151 | {class="label", label=msg, x=0, y=0,width=12}, 152 | {class="floatedit", name='float',value=default, x=0, y=1,width=12} 153 | } 154 | else 155 | error('display.confirm参数错误,无效的类型标识!',2) 156 | end 157 | 158 | -- 显示对话框 159 | btn, btnresult = aegisub.dialog.display(config,buttons,button_ids) 160 | 161 | -- 处理并返回结果 162 | if type_num == 0 then 163 | result = nil 164 | elseif type_num == 1 then 165 | if btn ~= false then 166 | result = true 167 | else 168 | result = false 169 | end 170 | elseif type_num == 2 then 171 | if btn ~= false then 172 | result = btnresult.text 173 | end 174 | elseif type_num == 3 then 175 | if btn ~= false then 176 | result = btnresult.int 177 | end 178 | elseif type_num == 4 then 179 | if btn ~= false then 180 | result = btnresult.float 181 | end 182 | end 183 | -- debug.var_export(result) 184 | return result 185 | end 186 | 187 | } 188 | } 189 | 190 | 191 | debug = cx_Aeg_Lua.debug 192 | display = cx_Aeg_Lua.display 193 | 194 | 195 | 196 | -- 来自karaskel的函数,从subs表收集样式和元数据(有大改) 197 | function karaskel_collect_head(subs) 198 | local meta = { 199 | -- X和Y脚本分辨率 200 | res_x = 0, res_y = 0, 201 | -- 视频/脚本分辨率不匹配的宽高比校正比值 202 | video_x_correct_factor = 1.0 203 | } 204 | local styles = { n = 0 } 205 | local first_style_line = nil -- 文件里的第一行样式位置 206 | 207 | -- 第一遍:收集所有现有样式并获取分辨率信息 208 | for i = 1, #subs do 209 | if aegisub.progress.is_cancelled() then error("User cancelled") end 210 | local l = subs[i] 211 | aegisub.progress.set((i/#subs)*4) 212 | if l.class == "style" then 213 | if not first_style_line then first_style_line = i end 214 | -- 将样式存储到样式表中 215 | styles.n = styles.n + 1 216 | styles[styles.n] = l 217 | styles[l.name] = l 218 | l.margin_v = l.margin_t -- 方便 219 | elseif l.class == "info" then 220 | local k = l.key:lower() 221 | meta[k] = l.value 222 | end 223 | end 224 | 225 | -- 修正解析度数据(分辨率数据?) 226 | if meta.playresx then 227 | meta.res_x = math.floor(meta.playresx) 228 | end 229 | if meta.playresy then 230 | meta.res_y = math.floor(meta.playresy) 231 | end 232 | if meta.res_x == 0 and meta_res_y == 0 then 233 | meta.res_x = 384 234 | meta.res_y = 288 235 | elseif meta.res_x == 0 then 236 | -- This is braindead, but it's how TextSub does things... 237 | -- 这真是令人头疼,但是这是TextSub做事的方式... 238 | if meta.res_y == 1024 then 239 | meta.res_x = 1280 240 | else 241 | meta.res_x = meta.res_y / 3 * 4 242 | end 243 | elseif meta.res_y == 0 then 244 | -- As if 1280x960 didn't exist 245 | -- 好像不存在1280x960 246 | if meta.res_x == 1280 then 247 | meta.res_y = 1024 248 | else 249 | meta.res_y = meta.res_x * 3 / 4 250 | end 251 | end 252 | 253 | local video_x, video_y = aegisub.video_size() 254 | if video_y then 255 | -- 分辨率校正因子 256 | meta.video_x_correct_factor = 257 | (video_y / video_x) / (meta.res_y / meta.res_x) 258 | end 259 | return meta, styles 260 | end 261 | 262 | -- 来自karaskel的函数,计算行尺寸信息 263 | function karaskel_preproc_line_pos(meta,line) 264 | -- 有效边距 265 | line.margin_v = line.margin_t 266 | line.eff_margin_l = ((line.margin_l > 0) and line.margin_l) or line.styleref.margin_l 267 | line.eff_margin_r = ((line.margin_r > 0) and line.margin_r) or line.styleref.margin_r 268 | line.eff_margin_t = ((line.margin_t > 0) and line.margin_t) or line.styleref.margin_t 269 | line.eff_margin_b = ((line.margin_b > 0) and line.margin_b) or line.styleref.margin_b 270 | line.eff_margin_v = ((line.margin_v > 0) and line.margin_v) or line.styleref.margin_v 271 | -- 以及定位 272 | if line.styleref.align == 1 or line.styleref.align == 4 or line.styleref.align == 7 then 273 | -- Left aligned::左对齐 274 | line.left = line.eff_margin_l 275 | line.center = line.left + line.width / 2 276 | line.right = line.left + line.width 277 | line.x = line.left 278 | line.halign = "left" 279 | elseif line.styleref.align == 2 or line.styleref.align == 5 or line.styleref.align == 8 then 280 | -- Centered::中心对齐 281 | line.left = (meta.res_x - line.eff_margin_l - line.eff_margin_r - line.width) / 2 + line.eff_margin_l 282 | line.center = line.left + line.width / 2 283 | line.right = line.left + line.width 284 | line.x = line.center 285 | line.halign = "center" 286 | elseif line.styleref.align == 3 or line.styleref.align == 6 or line.styleref.align == 9 then 287 | -- Right aligned::右对齐 288 | line.left = meta.res_x - line.eff_margin_r - line.width 289 | line.center = line.left + line.width / 2 290 | line.right = line.left + line.width 291 | line.x = line.right 292 | line.halign = "right" 293 | end 294 | line.hcenter = line.center 295 | if line.styleref.align >=1 and line.styleref.align <= 3 then 296 | -- Bottom aligned::底部对齐 297 | line.bottom = meta.res_y - line.eff_margin_b 298 | line.middle = line.bottom - line.height / 2 299 | line.top = line.bottom - line.height 300 | line.y = line.bottom 301 | line.valign = "bottom" 302 | elseif line.styleref.align >= 4 and line.styleref.align <= 6 then 303 | -- Mid aligned::中间对齐 304 | line.top = (meta.res_y - line.eff_margin_t - line.eff_margin_b - line.height) / 2 + line.eff_margin_t 305 | line.middle = line.top + line.height / 2 306 | line.bottom = line.top + line.height 307 | line.y = line.middle 308 | line.valign = "middle" 309 | elseif line.styleref.align >= 7 and line.styleref.align <= 9 then 310 | -- Top aligned::顶部对齐 311 | line.top = line.eff_margin_t 312 | line.middle = line.top + line.height / 2 313 | line.bottom = line.top + line.height 314 | line.y = line.top 315 | line.valign = "top" 316 | end 317 | line.vcenter = line.middle 318 | end 319 | 320 | -- table表的copy函数(来自Yutils) 321 | function table.copy(t, depth) 322 | -- Check argument 323 | if type(t) ~= "table" or depth ~= nil and not(type(depth) == "number" and depth >= 1) then 324 | error("table and optional depth expected", 2) 325 | end 326 | -- Copy & return 327 | local function copy_recursive(old_t) 328 | local new_t = {} 329 | for key, value in pairs(old_t) do 330 | new_t[key] = type(value) == "table" and copy_recursive(value) or value 331 | end 332 | return new_t 333 | end 334 | local function copy_recursive_n(old_t, depth) 335 | local new_t = {} 336 | for key, value in pairs(old_t) do 337 | new_t[key] = type(value) == "table" and depth >= 2 and copy_recursive_n(value, depth-1) or value 338 | end 339 | return new_t 340 | end 341 | return depth and copy_recursive_n(t, depth) or copy_recursive(t) 342 | end 343 | 344 | 345 | -- 计算行尺寸信息,参数(元信息表,行对应样式,行对象) 346 | function line_math_pos(meta,style,line) 347 | line.styleref = style 348 | 349 | -- 计算行的尺寸信息 350 | line.width, line.height, line.descent, line.ext_lead = aegisub.text_extents(style, line.text) 351 | line.width = line.width * meta.video_x_correct_factor 352 | 353 | -- 计算行的布局信息 354 | karaskel_preproc_line_pos(meta,line) 355 | 356 | end 357 | 358 | 359 | 360 | -- 行错误检测,收集并选择可能错误的所有行信息,参数(字幕对象,样式表,元信息表),返回值新选择行,错误计数 361 | -- 旗下函数通用返回值 bool-是否存在问题,line-行,msg-错误信息 362 | function subs_check_line(subs,styles,meta,select_check) 363 | new_selected = {} 364 | -- 默认检测 365 | select_check_def = { 366 | size = true,-- 尺寸异常 367 | error_char = true, -- 乱码字符 368 | style = true -- 样式不存在 369 | } 370 | if not select_check then 371 | select_check = select_check_def 372 | end 373 | -- 行尺寸检测设置 374 | line_size_check = true 375 | -- 定义筛选后用于返回后续处理的行表(允许判断的有效行下标) 376 | local dialogues = {} -- 对话行的表(start_line对话行开始下标) 377 | local styles_less = {} -- 样式不存在记录 378 | -- 获取视频信息 379 | video_xres, video_yres, video_ar, video_artype = aegisub.video_size() 380 | if not(video_xres and video_yres) then 381 | line_size_check = false 382 | debug.println('行尺寸检测:检测到未打开视频,此功能已禁用') 383 | else 384 | debug.println('行错误检测:视频分辨率信息 '..video_xres..'x'..video_yres) 385 | end 386 | 387 | -- 计数表 388 | cout = { 389 | check_line_total = 0, -- 检测行的总数 390 | dialogue_effect_total = 0, -- 对话行中特效不为空的行 391 | dialogue_actor_total = 0, -- 对话行中说话人不为空的行 392 | dialogue_comment_total = 0, -- 对话行中的注释行总数 393 | check_total = 0, -- 检测的总行数 394 | ignore_total = 0,-- 忽略行总数 395 | err_total = 0, -- 错误行总数 396 | err_size = 0, -- 错误行中的尺寸异常行计数 397 | err_char = 0, -- 错误行中的可疑字符行计数 398 | err_style = 0, -- 错误行中的使用不存在样式的行计数 399 | err_styles_str = '' -- 错误样式的文字信息 400 | } 401 | -- 存储异常样式的表 402 | err_styles = {} 403 | -- 存储异常样式文字版 404 | err_styles_str = '' 405 | -- 对话行行数存储 406 | line_n = 0 407 | debug.println('行错误检测:开始检测行') 408 | -- debug.var_dump(meta) 409 | --debug.var_dump(styles) 410 | 411 | -- 编译正则表达式 412 | expr = re.compile([[(\{[\s\S]+?\}){1}]],re.NOSUB) 413 | 414 | for i = 1,#subs do 415 | if aegisub.progress.is_cancelled() then 416 | debug.println('\n复查助手:'..'用户取消操作\n\n') 417 | aegisub.cancel() 418 | end 419 | if subs[i].class == 'dialogue' then 420 | aegisub.progress.set((i/#subs)*80 + 10) 421 | line_n = line_n + 1 422 | -- 检测到对话行 423 | line = subs[i] 424 | -- 只判断特效与说话人都未设置且没有被注释的行 425 | if line.comment then 426 | cout.dialogue_comment_total = cout.dialogue_comment_total + 1 427 | end 428 | if line.effect ~= '' then 429 | cout.dialogue_effect_total = cout.dialogue_effect_total + 1 430 | end 431 | if line.actor ~= '' then 432 | cout.dialogue_actor_total = cout.dialogue_actor_total + 1 433 | end 434 | 435 | if not line.comment and line.effect == '' and line.actor == '' then 436 | -- 判断该行是否存在ASS标签,存在ASS标签则忽略此行 437 | result = expr:match(line.text) 438 | if not result then 439 | -- 筛选后的行:非注释、不存在ASS标签且特效与说话人为空 440 | 441 | -- 插入行解析数据(排除不解析的行) 442 | table.insert(dialogues,{pos = i,start_time = line.start_time,end_time = line.end_time,style = line.style}) 443 | --[[ 444 | 属性: 445 | pos -- 行下标 446 | start_time -- 该行开始时间 447 | end_time -- 该行结束时间 448 | style -- 该行样式 449 | ]] 450 | 451 | -- 异常标记,检测完成后若有错误则为true 452 | typebool = false 453 | 454 | -- 检查是否含有可疑字符(可能导致压制错误) 455 | bool,line,msg = line_check_char(line) 456 | if bool then 457 | if select_check.error_char then 458 | typebool = true 459 | debug.println('乱码字符检测:第'..line_n..'行,'..msg) 460 | end 461 | cout.err_char = cout.err_char + 1 462 | end 463 | 464 | 465 | style = styles[line.style] 466 | if style then 467 | if line_size_check then 468 | -- 只有对应样式存在且允许执行时执行此函数 469 | bool,line,msg = line_check_width(meta,style,line) 470 | if bool then 471 | if select_check.size then 472 | typebool = true 473 | debug.println('行尺寸检测:第'..line_n..'行,'..msg) 474 | end 475 | cout.err_size = cout.err_size + 1 476 | end 477 | end 478 | else 479 | -- 样式不存在 480 | if select_check.style then 481 | -- 允许检查 482 | if not err_styles[line.style] then 483 | -- 此前没有检测出这个错误样式 484 | typebool = true 485 | -- 添加到检查表 486 | err_styles[line.style] = true 487 | if err_styles_str == '' then 488 | err_styles_str = line.style 489 | else 490 | err_styles_str = err_styles_str..' , '..line.style 491 | end 492 | 493 | end 494 | msg = '样式 '..line.style..' 不存在' 495 | debug.println('行样式检测:第'..line_n..'行,'..msg) 496 | end 497 | cout.err_style = cout.err_style + 1 498 | end 499 | 500 | if typebool then 501 | -- 将异常行置入新选择表 502 | subs[i] = line 503 | table.insert(new_selected,i) 504 | end 505 | else 506 | cout.ignore_total = cout.ignore_total + 1 507 | end 508 | else 509 | cout.ignore_total = cout.ignore_total + 1 510 | end 511 | end 512 | end 513 | cout.check_line_total = line_n 514 | cout.check_total = #subs 515 | cout.err_total = #new_selected 516 | 517 | -- 添加错误样式记录文字版 518 | cout.err_styles_str = err_styles_str 519 | return new_selected,cout 520 | end 521 | 522 | -- 标准检测函数 523 | function line_check_default(line) 524 | 525 | return false,line,'' 526 | end 527 | 528 | -- 行尺寸检测 检测行是否超出屏幕显示范围 529 | function line_check_width(meta,style,line) 530 | -- 拷贝当前行到临时变量 531 | temp_line = table.copy(line) 532 | -- 计算整行尺寸 533 | line_math_pos(meta,style,temp_line) 534 | 535 | -- 检测到越界时修改为true 536 | fix_type = false 537 | msg = '' 538 | --[[ 539 | line.left 行的左边缘X坐标,假设其给定对齐,有效边距并且没有碰撞检测 540 | line.center 行中心X坐标,假设其给定对齐,有效边距并且没有碰撞检测 541 | line.right 行的右边X坐标,假设其给定对齐,有效边距并且没有碰撞检测 542 | line.top 行的顶边Y坐标,假设其给定对齐,有效边距并且没有碰撞检测 543 | line.middle 行垂直中心 Y 坐标,假定其给定对齐,有效边距和无碰撞检测 line.vcenter是此的别名 544 | line.bottom 行的下边Y坐标,假设其给定对齐,有效边距并且没有碰撞检测 545 | meta.playresy and meta.playresx 546 | ]] 547 | --debug.var_dump(meta) 548 | --debug.var_dump(temp_line) 549 | 550 | if temp_line.left < 0 or temp_line.left > meta.res_x then 551 | fix_type = true 552 | msg = msg..' 左越界' 553 | end 554 | if temp_line.right > meta.res_x then 555 | fix_type = true 556 | msg = msg..' 右越界' 557 | end 558 | if temp_line.top < 0 or temp_line.top > meta.res_y then 559 | fix_type = true 560 | msg = msg..' 上越界' 561 | end 562 | if temp_line.bottom > meta.res_y then 563 | fix_type = true 564 | msg = msg..' 下越界' 565 | end 566 | if fix_type then 567 | return true,line,msg 568 | end 569 | return false,line,'' 570 | end 571 | 572 | -- 可能会引起压制错误的字符 573 | --[[ 574 | ㊚ 575 | ₂ 576 | ㊤ 577 | ₃ 578 | ㊛ 579 | ㊧ 580 | ㊥ 581 | ₄ 582 | ㊨ 583 | ㊙ 584 | ㊦ 585 | ▦ 586 | ▧ 587 | ㎥ 588 | ▤ 589 | ▥ 590 | ⁴ 591 | ▨ 592 | ▩ 593 | ・ 594 | ♬ 595 | ☞ 596 | ◑ 597 | ₁ 598 | ◐ 599 | ☜ 600 | ▷ 601 | ◁ 602 | ♢ 603 | ♤ 604 | ♧ 605 | ♡ 606 | ▶ 607 | ◀ 608 | ㏘ 609 | 610 | 611 | 612 | ㊚₂㊤₃㊛㊧㊥₄㊨㊙㊦▦▧㎥▤▥⁴▨▩・♬☞◑₁◐☜▷◁♢♤♧♡▶◀㏘ 613 | ]] 614 | local check_char = [[^\u2E80-\uFE4F]] 615 | -- 检测并标记任何查找到的可能导致压制错误的行 616 | function line_check_char(line) 617 | -- 忽略存在ASS标签的行之后 618 | -- 检查是否存在可能引起压制错误的字符 619 | result = re.match(line.text, "["..check_char.."]+?",re.NOSUB) 620 | if result then 621 | -- 发现了存在可能引起压制错误的行 622 | msg = '检测到可疑字符 '..result[1].str..' 在第 '..result[1].first..'个字符' 623 | -- debug.var_dump(result) 624 | return true,line,msg 625 | end 626 | 627 | -- 没有问题 628 | return false,line,'' 629 | end 630 | 631 | 632 | -- 60FPS修复 633 | function fix_60fps(subs) 634 | debug.println('60FPS修复:'..'开始修复') 635 | -- 代码来自 Kiriko 的 60FPS修复 636 | for i = 1, #subs do 637 | if subs[i].class == "dialogue" then 638 | local line=subs[i] 639 | if line.start_time%50 == 0 and line.start_time ~= 0 then 640 | line.start_time=line.start_time+10 641 | end 642 | if line.end_time%50 == 0 then 643 | line.end_time=line.end_time+10 644 | end 645 | subs[i]=line 646 | end 647 | end 648 | debug.println('60FPS修复:'..'修复完成') 649 | end 650 | 651 | -- 使用指定样式渲染文本 652 | function text_to_shape(text,style) 653 | if style.class ~= 'style' then return end 654 | text = tostring(text) 655 | FONT_HANDLE = decode.create_font(style.fontname, style.bold, style.italic, style.underline, style.strikeout, style.fontsize) 656 | shape = FONT_HANDLE.text_to_shape(line.text) 657 | return shape 658 | end 659 | 660 | 661 | -- 样式检测 662 | -- 字幕对象解析,解析并检测样式以及检查是否存在未安装字体(字体检测需要Yutils支持) 663 | -- 返回值 bool-是否异常,styles-样式表,meta-元信息表,null_styles_str-未安装字体列表(文本型) 664 | function subs_check_style(subs) 665 | debug.println('样式检测:收集样式及元信息...') 666 | 667 | -- 未安装字体列表(文本型) 668 | null_styles_str = '' 669 | 670 | meta,styles = karaskel_collect_head(subs) 671 | aegisub.progress.set(8) 672 | err_num = 0 673 | debug.println('样式检测:脚本分辨率信息 '..meta.res_x..'x'..meta.res_y) 674 | debug.println('样式检测:收集到 '..#styles..' 个样式') 675 | 676 | if Yutils then 677 | debug.println('样式检测:开始检测未安装字体') 678 | -- 获取系统字体列表 679 | fonts = Yutils.decode.list_fonts(false) 680 | result_type = false 681 | 682 | -- 建立样式的字体表 683 | styles_front = {} 684 | for name,style in pairs(styles) do 685 | if name ~= 'n' then 686 | -- 插入表 687 | styles_front[style.fontname] = {} 688 | styles_front[style.fontname].name = name 689 | end 690 | end 691 | aegisub.progress.set(9) 692 | -- 对字体进行标记 693 | for i = 1,#fonts do 694 | if styles_front[fonts[i].name] then 695 | styles_front[fonts[i].name].check = true 696 | end 697 | end 698 | 699 | -- 输出错误信息(如果存在) 700 | for frontname,style_info in pairs(styles_front) do 701 | if not style_info.check then 702 | -- 这个鬼样式的对应字体不存在 703 | debug.println('样式检测:样式 '..style_info.name..' 的 '..frontname..' 字体未安装') 704 | if null_styles_str == '' then 705 | null_styles_str = frontname..'('..style_info.name..')' 706 | else 707 | null_styles_str = null_styles_str..' , '..frontname..'('..style_info.name..')' 708 | end 709 | err_num = err_num + 1 710 | end 711 | end 712 | 713 | else 714 | debug.println('样式检测:Yutils载入失败,字体检测无法运行') 715 | end 716 | aegisub.progress.set(10) 717 | debug.println('样式检测:检测完毕') 718 | return result_type,styles,meta,err_num,null_styles_str 719 | end 720 | 721 | -- 闪轴与叠轴检测的前置 722 | -- 字幕对象解析,解析并排序,返回排序后的数组(subs_sort_arr) 723 | -- 返回值 subs_sort_arr-排序后的数组,line_start-对话行起始编号 724 | function parse_sort(subs,basic_progress,add_progress) 725 | subs_sort_arr = {} 726 | line_start = 0 727 | -- 编译正则表达式 728 | expr = re.compile([[(\{[\s\S]+?\}){1}]],re.NOSUB) 729 | expr1 = re.compile([[[^\s]{1}]],re.NOSUB) 730 | 731 | if not basic_progress then basic_progress = 0 end 732 | if not add_progress then add_progress = 30 end 733 | 734 | -- 解析忽略非对话行、空行、注释行、带特效标签的行、特效不为空的行 735 | for i = 1,#subs do 736 | aegisub.progress.set( i/#subs * add_progress + basic_progress) 737 | line = subs[i] 738 | if line.class == 'dialogue' then 739 | if line_start == 0 then line_start = i - 1 end 740 | if line.text ~= '' then 741 | result = expr:match(line.text) 742 | iter = expr1:gfind(line.text) 743 | str, start_idx, end_idx = iter() -- 检测是否为纯空白行 744 | if start_idx and not result and not line.comment and line.effect == '' then 745 | table.insert (subs_sort_arr, { 746 | pos = i , 747 | line_id = i - line_start, 748 | start_time = line.start_time , 749 | end_time = line.end_time , 750 | style = line.style 751 | }) 752 | end 753 | end 754 | end 755 | end 756 | 757 | -- 排序函数 758 | local function sort_comp(element1, elemnet2) 759 | if element1 == nil then 760 | return false; 761 | end 762 | if elemnet2 == nil then 763 | return true; 764 | end 765 | return element1.start_time < elemnet2.start_time 766 | end 767 | table.sort(subs_sort_arr,sort_comp) 768 | 769 | return subs_sort_arr 770 | end 771 | 772 | -- 闪轴检测,参数 subs_sort_arr(parse_sort函数返回值),subs(存在时检测并修复闪轴) 773 | -- 返回值 新选择行 774 | function check_interval200(subs_sort_arr,subs) 775 | --[[ 776 | { 777 | pos = i , 778 | start_time = line.start_time , 779 | end_time = line.end_time , 780 | style = line.style 781 | } 782 | ]] 783 | 784 | -- 缓存每个样式的前一行数据 785 | style_chace = {} 786 | -- 选择行 787 | new_selected = {} 788 | 789 | for i,tl in ipairs(subs_sort_arr) do 790 | if style_chace[tl.style] then 791 | interva = tl.start_time - style_chace[tl.style].end_time 792 | if interva < 200 and interva > 0 then 793 | -- <200ms判断为闪轴 794 | -- 添加检测到的闪轴到新选择行 795 | table.insert(new_selected,style_chace[tl.style].pos) 796 | if subs then 797 | -- 如果字幕对象存在,就应该整活了 798 | -- 每行起始时间不变,上一行结束时间向后调至紧贴下一行起始时间 799 | pre_line = subs[style_chace[tl.style].pos] 800 | line = subs[tl.pos] 801 | pre_line.end_time = line.start_time 802 | subs[style_chace[tl.style].pos] = pre_line 803 | end 804 | 805 | end 806 | end 807 | style_chace[tl.style] = tl 808 | end 809 | 810 | return new_selected 811 | end 812 | 813 | -- 叠轴、灵异轴检测,参数 subs_sort_arr(parse_sort函数返回值) 814 | -- 返回值 新选择行 815 | function check_overlap(subs_sort_arr,new_selected,basic_progress,add_progress) 816 | -- 无法自动修复,这种怪操作要挨锤的 817 | -- 缓存每个样式的前一行数据 818 | style_chace = {} 819 | -- 选择行 820 | if not new_selected then new_selected = {} end 821 | if not basic_progress then basic_progress = 30 end 822 | if not add_progress then add_progress = 70 end 823 | for i,tl in ipairs(subs_sort_arr) do 824 | aegisub.progress.set( i/#subs_sort_arr * add_progress + basic_progress) 825 | if style_chace[tl.style] then 826 | -- 同一样式 本行的开始时间小于上一行的结束时间 (单人这种轴太怪了,多人同一个样式也挺怪的) 827 | if tl.start_time < style_chace[tl.style].end_time then 828 | debug.println("叠轴检测:".."第"..tl.line_id.."行发现叠轴") 829 | table.insert(new_selected,tl.pos) 830 | end 831 | -- 一行的开始时间大于结束时间的畸形种 832 | if tl.start_time > tl.end_time then 833 | aegisub.debug.out(0, "叠轴检测:".."第"..tl.line_id.."行发现灵异轴") 834 | table.insert(new_selected,tl.pos) 835 | end 836 | end 837 | style_chace[tl.style] = tl 838 | end 839 | return new_selected 840 | end 841 | 842 | -- 检测开始函数,正常规范三参数+检测限制参数 843 | function check_start(subs, selected_lines, active_line, select_check) 844 | --复查助手 845 | 846 | select_check_def = { 847 | size = true,-- 尺寸异常检测 848 | error_char = true, -- 乱码字符检测 849 | style = true, -- 样式不存在检测 850 | overlap = true -- 叠轴检测 851 | } 852 | if not select_check then 853 | select_check = select_check_def 854 | end 855 | 856 | aegisub.progress.task('复 查 助 手') 857 | aegisub.progress.set(0) 858 | --[[ 859 | if #subs > 10000 then 860 | debug.println('复查助手:检测到字幕行行数超过一万..') 861 | if not display.confirm('检测到行数超过一万行\n是否继续?\n继续运行,运行时间可能较长',1) then 862 | aegisub.progress.task0('复查助手运行结束') 863 | debug.println('自动复查:选择停止') 864 | aegisub.cancel() 865 | end 866 | debug.println('复查助手:继续运行...') 867 | end 868 | ]] 869 | 870 | msg = [[小助手提醒您: 871 | 1.智能60FPS修复(视频已打开)(自动) 872 | 2.识别可能导致压制乱码的字符 873 | 3.识别单行字幕过长(视频已打开) 874 | 4.识别不存在的样式 875 | 5.同样式非注释行重叠 876 | 注:文件名也可能导致压制乱码,请自行检查 877 | 注:注释、说话人或特效不为空的行将被忽略 878 | 确定后开始自动复查!]] 879 | debug.println(msg..'\n') 880 | if not display.confirm(msg,1) then 881 | aegisub.progress.task('复查助手运行结束') 882 | debug.println('复查助手:选择停止') 883 | aegisub.cancel() 884 | end 885 | 886 | debug.println('复查助手:检查开始\n') 887 | aegisub.progress.set(1) 888 | -- 智能60FPS修复 889 | aegisub.progress.task('智能60FPS修复') 890 | debug.println('智能60FPS修复...') 891 | -- 判断前10s的帧数(视频需要至少10S长...) 892 | frame = aegisub.frame_from_ms(10000) 893 | if frame and frame > 500 and frame < 700 then 894 | debug.println('智能60FPS修复:'..'判断为60FPS') 895 | -- 这种情况就可以判定为60FPS了 896 | fix_60fps(subs) 897 | else 898 | debug.println('智能60FPS修复:'..'视频不为60FPS或视频未打开,跳过修复') 899 | end 900 | aegisub.progress.set(5) 901 | --debug.println('智能60FPS修复执行完毕...') 902 | debug.println() 903 | 904 | 905 | -- 样式检测 906 | aegisub.progress.task('样式字体检测...') 907 | style_check,styles,meta,style_check_err_num,style_check_str = subs_check_style(subs) 908 | debug.println() 909 | 910 | aegisub.progress.set(10) 911 | -- 行错误检测 912 | aegisub.progress.task('行错误检测') 913 | debug.println('行错误检测...') 914 | new_selected,check_line_cout = subs_check_line(subs,styles,meta,select_check) 915 | --debug.println('行错误检测执行完毕...') 916 | 917 | aegisub.progress.set(90) 918 | aegisub.progress.task('叠轴检测') 919 | overlap_line_n = 0 920 | if select_check.overlap then 921 | -- 解析 922 | subs_sort_arr = parse_sort(subs,90,3) 923 | aegisub.progress.set(93) 924 | -- 检测行(更新选择) 925 | chace_ns = #new_selected 926 | new_selected = check_overlap(subs_sort_arr,new_selected,93,7) 927 | 928 | -- 叠轴行数 929 | overlap_line_n = #new_selected - chace_ns 930 | end 931 | 932 | 933 | aegisub.progress.set(100) 934 | debug.println() 935 | aegisub.progress.task('复查助手运行结束') 936 | debug.println('====复查助手·统计====') 937 | if style_check or #new_selected ~= 0 or style_check_err_num ~= 0 then 938 | debug.println('?:这ASS怪怪的') 939 | debug.println('实际检测行:'..check_line_cout.check_line_total-check_line_cout.ignore_total) 940 | if Yutils then 941 | debug.println('字体未安装:'..style_check_err_num) 942 | if style_check_err_num ~= 0 then 943 | debug.println('未安装字体(所属样式):'..style_check_str) 944 | end 945 | else 946 | debug.println('未安装字体检测:警告,未安装Yutils,检测无法运行。') 947 | end 948 | if select_check.overlap then 949 | if overlap_line_n ~= 0 then 950 | debug.println('!!!警告,当前叠轴行数不为0,建议仔细检查!!!') 951 | end 952 | debug.println('叠轴行数:'..overlap_line_n) 953 | end 954 | debug.println('异常对话行总数(不计叠轴):'..check_line_cout.err_total) 955 | if check_line_cout.err_total ~= 0 then 956 | if select_check.size then 957 | debug.println('尺寸过大的行:'..check_line_cout.err_size) 958 | end 959 | if select_check.error_char then 960 | debug.println('存在可疑字符的行:'..check_line_cout.err_char) 961 | end 962 | if select_check.style then 963 | debug.println('使用不存在样式的行:'..check_line_cout.err_style) 964 | if check_line_cout.err_style ~= 0 then 965 | debug.println('不存在的样式:'..cout.err_styles_str) 966 | end 967 | end 968 | end 969 | debug.println('所有检测到的异常行已经标记\n关闭窗口后显示标记结果') 970 | else 971 | debug.println('所有检测执行完毕') 972 | debug.println('总识别行:'..#subs) 973 | debug.println('总检测行:'..check_line_cout.check_line_total-check_line_cout.ignore_total) 974 | if not Yutils then 975 | debug.println('未安装字体检测:警告,未安装Yutils,检测无法运行。') 976 | end 977 | debug.println('特效不为空的行:'..check_line_cout.dialogue_effect_total) 978 | debug.println('忽略检测行总数:'..check_line_cout.ignore_total) 979 | debug.println('说话人不为空的行:'..check_line_cout.dialogue_actor_total) 980 | debug.println('这个ASS看起来没什么不对(') 981 | end 982 | 983 | 984 | if #new_selected == 0 then 985 | return 986 | else 987 | return new_selected 988 | end 989 | end 990 | 991 | 992 | 993 | -- 菜单的选择启动函数 994 | 995 | function macro_main(subs, selected_lines, active_line) 996 | -- 修改全局提示等级 997 | debug.changelevel(3) 998 | -- 默认全都检查 999 | return check_start(subs, selected_lines, active_line) 1000 | end 1001 | 1002 | function macro_select_sizeover(subs, selected_lines, active_line) 1003 | -- 你这行,太长了吧? 1004 | -- 修改全局提示等级 1005 | debug.changelevel(4) 1006 | select_check = { 1007 | size = true,-- 尺寸异常检测 1008 | error_char = false, -- 乱码字符检测 1009 | style = false, -- 样式不存在检测 1010 | overlap = false -- 叠轴检测 1011 | } 1012 | -- 检查限制 1013 | return check_start(subs, selected_lines, active_line, select_check) 1014 | end 1015 | 1016 | function macro_select_errchar(subs, selected_lines, active_line) 1017 | -- zai?为什么用特殊字符,还是这种特殊字符? 1018 | -- 修改全局提示等级 1019 | debug.changelevel(4) 1020 | select_check = { 1021 | size = false,-- 尺寸异常检测 1022 | error_char = true, -- 乱码字符检测 1023 | style = false, -- 样式不存在检测 1024 | overlap = false -- 叠轴检测 1025 | } 1026 | -- 检查限制 1027 | return check_start(subs, selected_lines, active_line, select_check) 1028 | end 1029 | 1030 | function macro_select_nullstyle(subs, selected_lines, active_line) 1031 | -- 这样式咋空了 1032 | -- 修改全局提示等级 1033 | debug.changelevel(3) 1034 | select_check = { 1035 | size = false,-- 尺寸异常检测 1036 | error_char = false, -- 乱码字符检测 1037 | style = true, -- 样式不存在检测 1038 | overlap = false -- 叠轴检测 1039 | } 1040 | -- 检查限制 1041 | return check_start(subs, selected_lines, active_line, select_check) 1042 | end 1043 | 1044 | function macro_select_basic(subs, selected_lines, active_line) 1045 | -- 最基础的检查 1046 | -- 修改全局提示等级 1047 | debug.changelevel(3) 1048 | select_check = { 1049 | size = false,-- 尺寸异常检测 1050 | error_char = false, -- 乱码字符检测 1051 | style = false, -- 样式不存在检测 1052 | overlap = false -- 叠轴检测 1053 | } 1054 | -- 检查限制 1055 | return check_start(subs, selected_lines, active_line, select_check) 1056 | end 1057 | 1058 | function macro_interval200(subs, selected_lines, active_line) 1059 | -- 检查行间隔是否<200ms 1060 | -- 修改全局提示等级 1061 | debug.changelevel(4) 1062 | -- 解析 1063 | subs_sort_arr = parse_sort(subs) 1064 | -- 判断行 1065 | new_selected = check_interval200(subs_sort_arr) 1066 | if #new_selected ~= 0 then 1067 | return new_selected 1068 | end 1069 | display.confirm('未发现间隔小于200ms的行',0) 1070 | end 1071 | 1072 | function macro_interval200_fix(subs, selected_lines, active_line) 1073 | -- 检查行间隔是否<200ms 1074 | -- 修改全局提示等级 1075 | debug.changelevel(4) 1076 | msg = '是否确认进行修复?' 1077 | debug.println(msg..'\n') 1078 | if not display.confirm(msg,1) then 1079 | aegisub.progress.task('复查助手运行结束') 1080 | debug.println('复查助手:选择停止') 1081 | aegisub.cancel() 1082 | end 1083 | 1084 | 1085 | -- 解析 1086 | subs_sort_arr = parse_sort(subs) 1087 | -- 判断行 1088 | new_selected = check_interval200(subs_sort_arr,subs) 1089 | 1090 | if #new_selected ~= 0 then 1091 | return new_selected 1092 | end 1093 | display.confirm('未发现间隔小于200ms的行',0) 1094 | end 1095 | 1096 | function macro_select_overlap(subs, selected_lines, active_line) 1097 | aegisub.progress.task('复 查 助 手') 1098 | -- 兄啊,同一个人说话怎么叠一起的? 1099 | -- 修改全局提示等级 1100 | debug.changelevel(3) 1101 | msg = [[小助手提醒您: 1102 | 1.智能60FPS修复(视频已打开)(自动) 1103 | 2.识别可能导致压制乱码的字符 1104 | 3.识别单行字幕过长(视频已打开) 1105 | 4.识别不存在的样式 1106 | 5.同样式非注释行重叠 1107 | 注:文件名也可能导致压制乱码,请自行检查 1108 | 注:注释、说话人或特效不为空的行将被忽略]] 1109 | debug.println(msg..'\n') 1110 | aegisub.progress.set(0) 1111 | -- 解析 1112 | subs_sort_arr = parse_sort(subs) 1113 | aegisub.progress.set(30) 1114 | -- 检测行 1115 | new_selected = check_overlap(subs_sort_arr) 1116 | aegisub.progress.set(100) 1117 | debug.println() 1118 | debug.println() 1119 | debug.println('====复查助手·统计====') 1120 | if #new_selected ~= 0 then 1121 | debug.println('如无意外,此轴可锤') 1122 | debug.println('叠轴计数:'..#new_selected) 1123 | return new_selected 1124 | end 1125 | debug.println('看起来没有叠轴的存在呢') 1126 | display.confirm('未发现可能存在的叠轴',0) 1127 | end 1128 | 1129 | function macro_fix60fps(subs, selected_lines, active_line) 1130 | debug.changelevel(4) 1131 | fix_60fps(subs) 1132 | display.confirm('我跟你说,它 好 了'.."\n"..'*此功能重复使用无影响',0) 1133 | end 1134 | 1135 | function macro_about() 1136 | -- 修改全局提示等级 1137 | debug.changelevel(1) 1138 | version_log = [[ 1139 | 更新日志: 1140 | 1.2.0 Alpha 2019-12-20 1141 | ·更新了特殊符号检测算法,可以检测常用字符以外的符号。 1142 | ·检测所有除了中日韩统一表意文字(CJK Unified Ideographs)之外的特殊字符。 1143 | ]] 1144 | msg = '复查小助手 '..script_version.."\n".. 1145 | [[ 1146 | 1147 | 本助手的功能有 1148 | 1.智能60FPS修复(视频已打开)(自动) 1149 | 2.识别可能导致压制乱码的字符 1150 | 3.识别单行字幕过长(视频已打开) 1151 | 4.识别不存在的样式 1152 | 5.同样式非注释行重叠以及包含 1153 | 6.闪轴检测及修复(行间隔<200ms) 1154 | 注:60FPS修复经过测试多次使用对ASS无负面影响 1155 | 注:闪轴检测为独立功能,仅在菜单内提供,不自动使用,并且提供自动修复选项。 1156 | 注:文件名也可能导致压制乱码,请自行检查 1157 | 注:注释、说话人或特效不为空的行将被忽略 1158 | 注:本助手的提示等级为level 3 如果不能正常显示信息或者其他异常请检查您的设置 1159 | 注:本插件所做修改可由AEG的撤销功能撤回 1160 | 作者:晨轩°(3309003591) 1161 | 本关于的最后修改时间:2019-12-16 01:39:28 1162 | ]] 1163 | debug.println(msg) 1164 | end 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | -- 注册AEG菜单 1171 | 1172 | aegisub.register_macro(script_name, script_description, macro_main, macro_can_use) 1173 | 1174 | aegisub.register_macro(script_name.."-菜单".."/基本检查", "查查更健康", macro_select_basic, macro_can_use) 1175 | 1176 | aegisub.register_macro(script_name.."-菜单".."/独立检测/选择尺寸过大的行", "太长是不好的", macro_select_sizeover, macro_can_use) 1177 | aegisub.register_macro(script_name.."-菜单".."/独立检测/选择含可疑字符行", "可能引起压制错误的行", macro_select_errchar, macro_can_use) 1178 | aegisub.register_macro(script_name.."-菜单".."/独立检测/检测不存在的样式", "兄啊,你这有点不对劲啊", macro_select_nullstyle, macro_can_use) 1179 | aegisub.register_macro(script_name.."-菜单".."/独立检测/检测叠轴", "兄啊,同一个人说话怎么叠一起的?", macro_select_overlap, macro_can_use) 1180 | 1181 | 1182 | aegisub.register_macro(script_name.."-菜单".."/检测字幕闪现(行间隔<200ms)", "这ass费眼睛", macro_interval200, macro_can_use) 1183 | aegisub.register_macro(script_name.."-菜单".."/修复字幕闪现(行间隔<200ms)", "它不会费眼睛了", macro_interval200_fix, macro_can_use) 1184 | aegisub.register_macro(script_name.."-菜单".."/60fps修复", "这个60FPS的视频看起来中暑了,不如我们...", macro_fix60fps, macro_can_use) 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | aegisub.register_macro(script_name.."-菜单".."/关于", "一些说明", macro_about, macro_can_use) -------------------------------------------------------------------------------- /复查助手-已发布版本存档/AEG复查小助手v1.3.0 Alpha[2019年1月20日]-包含安装说明 需解压.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/AEG复查小助手v1.3.0 Alpha[2019年1月20日]-包含安装说明 需解压.zip -------------------------------------------------------------------------------- /复查助手-已发布版本存档/AEG复查小助手v1.3.2 Alpha[2020年1月27日].lua: -------------------------------------------------------------------------------- 1 | local tr = aegisub.gettext 2 | 3 | script_name = tr"复查小助手" 4 | script_description = tr"检查字幕里可能存在的常见问题,并自动修复或提示,部分功能依赖Yutils" 5 | script_author = "晨轩°" 6 | script_version = "1.3.2 Alpha" 7 | 8 | -- 载入re库 正则表达 9 | local re = require 'aegisub.re' 10 | 11 | include("utils.lua") 12 | 13 | local config = { 14 | -- 自定义白名单字符(添加进[[括号内]],例如 while_errchar = [[#$@]]) 15 | while_errchar = [[]] 16 | } 17 | 18 | 19 | -- 辅助支持表 20 | local default_config = { 21 | debug = { 22 | level = 4 23 | } 24 | } 25 | cx_Aeg_Lua = { 26 | debug = { 27 | -- 调试输出定义 28 | -- 设置调试输出等级 29 | level = default_config.debug.level 30 | -- 修改调试输出等级(0~5 设置大于5的等级时不会输出任何信息,设置值小于0时会被置0) 31 | ,changelevel = function (level) 32 | if type(level) ~= 'number' then error('错误的调试等级设置!',2) end 33 | if level < 0 then level = 0 end 34 | cx_Aeg_Lua.debug.level = level 35 | end 36 | -- 普通输出(带换行、不带换行) 37 | ,println = function (msg) 38 | if cx_Aeg_Lua.debug.level > 5 then return end 39 | if msg ~= nil then 40 | aegisub.debug.out(cx_Aeg_Lua.debug.level, tostring(msg).."\n") 41 | else 42 | aegisub.debug.out(cx_Aeg_Lua.debug.level, "\n") 43 | end 44 | end 45 | ,print = function (msg) 46 | if cx_Aeg_Lua.debug.level > 5 then return end 47 | if msg == nil then return end 48 | aegisub.debug.out(cx_Aeg_Lua.debug.level, tostring(msg)) 49 | end 50 | 51 | -- 输出一个变量的字符串表示 52 | ,var_export = function (value) 53 | aegisub.debug.out(cx_Aeg_Lua.debug.level, '('..type(value)..')'..tostring(value)..'\n') 54 | end 55 | -- 直接输出一个表达式结构信息 56 | ,var_dump = function (value) 57 | -- print覆盖 58 | local function print(msg) 59 | cx_Aeg_Lua.debug.println(msg) 60 | end 61 | -- 好用的table输出函数 62 | local function print_r(t) 63 | local print_r_cache={} 64 | local function sub_print_r(t,indent) 65 | if (print_r_cache[tostring(t)]) then 66 | print(indent.."*"..tostring(t)) 67 | else 68 | print_r_cache[tostring(t)]=true 69 | if (type(t)=="table") then 70 | for pos,val in pairs(t) do 71 | if (type(val)=="table") then 72 | print(indent.."["..pos.."] => "..tostring(t).." {") 73 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 74 | print(indent..string.rep(" ",string.len(pos)+6).."}") 75 | elseif (type(val)=="string") then 76 | print(indent.."["..pos..'] => "'..val..'"') 77 | else 78 | print(indent.."["..pos.."] => "..tostring(val)) 79 | end 80 | end 81 | else 82 | print(indent..tostring(t)) 83 | end 84 | end 85 | end 86 | if (type(t)=="table") then 87 | print(tostring(t).." {") 88 | sub_print_r(t," ") 89 | print("}") 90 | else 91 | sub_print_r(t," ") 92 | end 93 | print() 94 | end 95 | -- 运行函数 96 | print_r(value) 97 | -- 打印结果 98 | 99 | end 100 | 101 | } 102 | ,display = { 103 | -- 显示一个简单的提示窗口,参数(提示信息[,类型标识[,默认值]]) 104 | --[[ 类型标识(返回类型): 105 | 0-提示框(nil),提示需要每行尽可能短(不超过9个字) 106 | 1-确认取消框(bool) 107 | 2-单行文本输入框(string or nil) 108 | 3-单行整数输入框(number or nil) 109 | 4-单行小数输入框(number or nil) 110 | 注意:整数与小数输入有误时不会限制或报错,可能得到奇怪的结果。 111 | ]] 112 | confirm = function (msg,type_num,default) 113 | local config = {} -- 窗口配置 114 | local result = nil -- 返回结果 115 | local buttons = nil 116 | local button_ids = nil 117 | if type(msg) ~= 'string' then 118 | error('display.confirm参数错误-1,提示信息必须存在且为文本类型!',2) 119 | end 120 | if type_num == nil then type_num = 0 end 121 | if type(type_num) ~= 'number' then 122 | -- db.var_export(type_num) 123 | error('display.confirm参数错误-2,类型标识必须是数值!',2) 124 | end 125 | 126 | if type_num == 0 then 127 | config = { 128 | {class="label", label=msg, x=0, y=0,width=8} 129 | } 130 | buttons = {'OK!'} 131 | button_ids = {ok = 'OK!'} 132 | elseif type_num == 1 then 133 | config = { 134 | {class="label", label=msg, x=0, y=0,width=12} 135 | } 136 | elseif type_num == 2 then 137 | if default == nil then default = '' end 138 | config = { 139 | {class="label", label=msg, x=0, y=0,width=12}, 140 | {class="edit", name='text',text=tostring(default), x=0, y=1,width=12} 141 | } 142 | elseif type_num == 3 then 143 | if default == nil then default = 0 end 144 | if type(type_num) ~= 'number' then 145 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 146 | end 147 | config = { 148 | {class="label", label=msg, x=0, y=0,width=12}, 149 | {class="intedit", name='int',value=default, x=0, y=1,width=12} 150 | } 151 | elseif type_num == 4 then 152 | if default == nil then default = 0 end 153 | if type(type_num) ~= 'number' then 154 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 155 | end 156 | config = { 157 | {class="label", label=msg, x=0, y=0,width=12}, 158 | {class="floatedit", name='float',value=default, x=0, y=1,width=12} 159 | } 160 | else 161 | error('display.confirm参数错误,无效的类型标识!',2) 162 | end 163 | 164 | -- 显示对话框 165 | btn, btnresult = aegisub.dialog.display(config,buttons,button_ids) 166 | 167 | -- 处理并返回结果 168 | if type_num == 0 then 169 | result = nil 170 | elseif type_num == 1 then 171 | if btn ~= false then 172 | result = true 173 | else 174 | result = false 175 | end 176 | elseif type_num == 2 then 177 | if btn ~= false then 178 | result = btnresult.text 179 | end 180 | elseif type_num == 3 then 181 | if btn ~= false then 182 | result = btnresult.int 183 | end 184 | elseif type_num == 4 then 185 | if btn ~= false then 186 | result = btnresult.float 187 | end 188 | end 189 | -- debug.var_export(result) 190 | return result 191 | end 192 | 193 | } 194 | } 195 | 196 | 197 | debug = cx_Aeg_Lua.debug 198 | display = cx_Aeg_Lua.display 199 | 200 | 201 | 202 | -- 来自karaskel的函数,从subs表收集样式和元数据(有大改) 203 | function karaskel_collect_head(subs) 204 | local meta = { 205 | -- X和Y脚本分辨率 206 | res_x = 0, res_y = 0, 207 | -- 视频/脚本分辨率不匹配的宽高比校正比值 208 | video_x_correct_factor = 1.0 209 | } 210 | local styles = { n = 0 } 211 | local first_style_line = nil -- 文件里的第一行样式位置 212 | 213 | -- 第一遍:收集所有现有样式并获取分辨率信息 214 | for i = 1, #subs do 215 | if aegisub.progress.is_cancelled() then error("User cancelled") end 216 | local l = subs[i] 217 | aegisub.progress.set((i/#subs)*4) 218 | if l.class == "style" then 219 | if not first_style_line then first_style_line = i end 220 | -- 将样式存储到样式表中 221 | styles.n = styles.n + 1 222 | styles[styles.n] = l 223 | styles[l.name] = l 224 | l.margin_v = l.margin_t -- 方便 225 | elseif l.class == "info" then 226 | local k = l.key:lower() 227 | meta[k] = l.value 228 | end 229 | end 230 | 231 | -- 修正解析度数据(分辨率数据?) 232 | if meta.playresx then 233 | meta.res_x = math.floor(meta.playresx) 234 | end 235 | if meta.playresy then 236 | meta.res_y = math.floor(meta.playresy) 237 | end 238 | if meta.res_x == 0 and meta_res_y == 0 then 239 | meta.res_x = 384 240 | meta.res_y = 288 241 | elseif meta.res_x == 0 then 242 | -- This is braindead, but it's how TextSub does things... 243 | -- 这真是令人头疼,但是这是TextSub做事的方式... 244 | if meta.res_y == 1024 then 245 | meta.res_x = 1280 246 | else 247 | meta.res_x = meta.res_y / 3 * 4 248 | end 249 | elseif meta.res_y == 0 then 250 | -- As if 1280x960 didn't exist 251 | -- 好像不存在1280x960 252 | if meta.res_x == 1280 then 253 | meta.res_y = 1024 254 | else 255 | meta.res_y = meta.res_x * 3 / 4 256 | end 257 | end 258 | 259 | local video_x, video_y = aegisub.video_size() 260 | if video_y then 261 | -- 分辨率校正因子 262 | meta.video_x_correct_factor = 263 | (video_y / video_x) / (meta.res_y / meta.res_x) 264 | end 265 | return meta, styles 266 | end 267 | 268 | -- 来自karaskel的函数,计算行尺寸信息 269 | function karaskel_preproc_line_pos(meta,line) 270 | -- 有效边距 271 | line.margin_v = line.margin_t 272 | line.eff_margin_l = ((line.margin_l > 0) and line.margin_l) or line.styleref.margin_l 273 | line.eff_margin_r = ((line.margin_r > 0) and line.margin_r) or line.styleref.margin_r 274 | line.eff_margin_t = ((line.margin_t > 0) and line.margin_t) or line.styleref.margin_t 275 | line.eff_margin_b = ((line.margin_b > 0) and line.margin_b) or line.styleref.margin_b 276 | line.eff_margin_v = ((line.margin_v > 0) and line.margin_v) or line.styleref.margin_v 277 | -- 以及定位 278 | if line.styleref.align == 1 or line.styleref.align == 4 or line.styleref.align == 7 then 279 | -- Left aligned::左对齐 280 | line.left = line.eff_margin_l 281 | line.center = line.left + line.width / 2 282 | line.right = line.left + line.width 283 | line.x = line.left 284 | line.halign = "left" 285 | elseif line.styleref.align == 2 or line.styleref.align == 5 or line.styleref.align == 8 then 286 | -- Centered::中心对齐 287 | line.left = (meta.res_x - line.eff_margin_l - line.eff_margin_r - line.width) / 2 + line.eff_margin_l 288 | line.center = line.left + line.width / 2 289 | line.right = line.left + line.width 290 | line.x = line.center 291 | line.halign = "center" 292 | elseif line.styleref.align == 3 or line.styleref.align == 6 or line.styleref.align == 9 then 293 | -- Right aligned::右对齐 294 | line.left = meta.res_x - line.eff_margin_r - line.width 295 | line.center = line.left + line.width / 2 296 | line.right = line.left + line.width 297 | line.x = line.right 298 | line.halign = "right" 299 | end 300 | line.hcenter = line.center 301 | if line.styleref.align >=1 and line.styleref.align <= 3 then 302 | -- Bottom aligned::底部对齐 303 | line.bottom = meta.res_y - line.eff_margin_b 304 | line.middle = line.bottom - line.height / 2 305 | line.top = line.bottom - line.height 306 | line.y = line.bottom 307 | line.valign = "bottom" 308 | elseif line.styleref.align >= 4 and line.styleref.align <= 6 then 309 | -- Mid aligned::中间对齐 310 | line.top = (meta.res_y - line.eff_margin_t - line.eff_margin_b - line.height) / 2 + line.eff_margin_t 311 | line.middle = line.top + line.height / 2 312 | line.bottom = line.top + line.height 313 | line.y = line.middle 314 | line.valign = "middle" 315 | elseif line.styleref.align >= 7 and line.styleref.align <= 9 then 316 | -- Top aligned::顶部对齐 317 | line.top = line.eff_margin_t 318 | line.middle = line.top + line.height / 2 319 | line.bottom = line.top + line.height 320 | line.y = line.top 321 | line.valign = "top" 322 | end 323 | line.vcenter = line.middle 324 | end 325 | 326 | -- table表的copy函数(来自Yutils) 327 | function table.copy(t, depth) 328 | -- Check argument 329 | if type(t) ~= "table" or depth ~= nil and not(type(depth) == "number" and depth >= 1) then 330 | error("table and optional depth expected", 2) 331 | end 332 | -- Copy & return 333 | local function copy_recursive(old_t) 334 | local new_t = {} 335 | for key, value in pairs(old_t) do 336 | new_t[key] = type(value) == "table" and copy_recursive(value) or value 337 | end 338 | return new_t 339 | end 340 | local function copy_recursive_n(old_t, depth) 341 | local new_t = {} 342 | for key, value in pairs(old_t) do 343 | new_t[key] = type(value) == "table" and depth >= 2 and copy_recursive_n(value, depth-1) or value 344 | end 345 | return new_t 346 | end 347 | return depth and copy_recursive_n(t, depth) or copy_recursive(t) 348 | end 349 | 350 | 351 | -- 计算行尺寸信息,参数(元信息表,行对应样式,行对象) 352 | function line_math_pos(meta,style,line) 353 | line.styleref = style 354 | 355 | -- 计算行的尺寸信息 356 | line.width, line.height, line.descent, line.ext_lead = aegisub.text_extents(style, line.text) 357 | line.width = line.width * meta.video_x_correct_factor 358 | 359 | -- 计算行的布局信息 360 | karaskel_preproc_line_pos(meta,line) 361 | 362 | end 363 | 364 | 365 | 366 | -- 行错误检测,收集并选择可能错误的所有行信息,参数(字幕对象,样式表,元信息表),返回值新选择行,错误计数 367 | -- 旗下函数通用返回值 bool-是否存在问题,line-行,msg-错误信息 368 | function subs_check_line(subs,styles,meta,select_check) 369 | new_selected = {} 370 | -- 默认检测 371 | select_check_def = { 372 | size = true,-- 尺寸异常 373 | error_char = true, -- 乱码字符 374 | style = true -- 样式不存在 375 | } 376 | if not select_check then 377 | select_check = select_check_def 378 | end 379 | -- 行尺寸检测设置 380 | line_size_check = true 381 | -- 定义筛选后用于返回后续处理的行表(允许判断的有效行下标) 382 | local dialogues = {} -- 对话行的表(start_line对话行开始下标) 383 | local styles_less = {} -- 样式不存在记录 384 | -- 获取视频信息 385 | video_xres, video_yres, video_ar, video_artype = aegisub.video_size() 386 | if not(video_xres and video_yres) then 387 | line_size_check = false 388 | debug.println('行尺寸检测:检测到未打开视频,此功能已禁用') 389 | else 390 | debug.println('行错误检测:视频分辨率信息 '..video_xres..'x'..video_yres) 391 | end 392 | 393 | -- 计数表 394 | cout = { 395 | check_line_total = 0, -- 检测行的总数 396 | dialogue_effect_total = 0, -- 对话行中特效不为空的行 397 | dialogue_actor_total = 0, -- 对话行中说话人不为空的行 398 | dialogue_comment_total = 0, -- 对话行中的注释行总数 399 | check_total = 0, -- 检测的总行数 400 | ignore_total = 0,-- 忽略行总数 401 | err_total = 0, -- 错误行总数 402 | err_size = 0, -- 错误行中的尺寸异常行计数 403 | err_char = 0, -- 错误行中的可疑字符行计数 404 | err_style = 0, -- 错误行中的使用不存在样式的行计数 405 | err_styles_str = '' -- 错误样式的文字信息 406 | } 407 | -- 存储异常样式的表 408 | err_styles = {} 409 | -- 存储异常样式文字版 410 | err_styles_str = '' 411 | -- 对话行行数存储 412 | line_n = 0 413 | debug.println('行错误检测:开始检测行') 414 | -- debug.var_dump(meta) 415 | --debug.var_dump(styles) 416 | 417 | -- 编译正则表达式 418 | expr = re.compile([[(\{[\s\S]+?\}){1}]],re.NOSUB) 419 | 420 | for i = 1,#subs do 421 | if aegisub.progress.is_cancelled() then 422 | debug.println('\n复查助手:'..'用户取消操作\n\n') 423 | aegisub.cancel() 424 | end 425 | if subs[i].class == 'dialogue' then 426 | aegisub.progress.set((i/#subs)*80 + 10) 427 | line_n = line_n + 1 428 | -- 检测到对话行 429 | line = subs[i] 430 | -- 只判断特效与说话人都未设置且没有被注释的行 431 | if line.comment then 432 | cout.dialogue_comment_total = cout.dialogue_comment_total + 1 433 | end 434 | if line.effect ~= '' then 435 | cout.dialogue_effect_total = cout.dialogue_effect_total + 1 436 | end 437 | if line.actor ~= '' then 438 | cout.dialogue_actor_total = cout.dialogue_actor_total + 1 439 | end 440 | 441 | if not line.comment and line.effect == '' and line.actor == '' then 442 | -- 判断该行是否存在ASS标签,存在ASS标签则忽略此行 443 | result = expr:match(line.text) 444 | if not result then 445 | -- 筛选后的行:非注释、不存在ASS标签且特效与说话人为空 446 | 447 | -- 插入行解析数据(排除不解析的行) 448 | table.insert(dialogues,{pos = i,start_time = line.start_time,end_time = line.end_time,style = line.style}) 449 | --[[ 450 | 属性: 451 | pos -- 行下标 452 | start_time -- 该行开始时间 453 | end_time -- 该行结束时间 454 | style -- 该行样式 455 | ]] 456 | 457 | -- 异常标记,检测完成后若有错误则为true 458 | typebool = false 459 | 460 | -- 检查是否含有可疑字符(可能导致压制错误) 461 | bool,line,msg = line_check_char(line) 462 | if bool then 463 | if select_check.error_char then 464 | typebool = true 465 | debug.println('乱码字符检测:第'..line_n..'行,'..msg) 466 | end 467 | cout.err_char = cout.err_char + 1 468 | end 469 | 470 | 471 | style = styles[line.style] 472 | if style then 473 | if line_size_check then 474 | -- 只有对应样式存在且允许执行时执行此函数 475 | bool,line,msg = line_check_width(meta,style,line) 476 | if bool then 477 | if select_check.size then 478 | typebool = true 479 | debug.println('行尺寸检测:第'..line_n..'行,'..msg) 480 | end 481 | cout.err_size = cout.err_size + 1 482 | end 483 | end 484 | else 485 | -- 样式不存在 486 | if select_check.style then 487 | -- 允许检查 488 | if not err_styles[line.style] then 489 | -- 此前没有检测出这个错误样式 490 | typebool = true 491 | -- 添加到检查表 492 | err_styles[line.style] = true 493 | if err_styles_str == '' then 494 | err_styles_str = line.style 495 | else 496 | err_styles_str = err_styles_str..' , '..line.style 497 | end 498 | 499 | end 500 | msg = '样式 '..line.style..' 不存在' 501 | debug.println('行样式检测:第'..line_n..'行,'..msg) 502 | end 503 | cout.err_style = cout.err_style + 1 504 | end 505 | 506 | if typebool then 507 | -- 将异常行置入新选择表 508 | subs[i] = line 509 | table.insert(new_selected,i) 510 | end 511 | else 512 | cout.ignore_total = cout.ignore_total + 1 513 | end 514 | else 515 | cout.ignore_total = cout.ignore_total + 1 516 | end 517 | end 518 | end 519 | cout.check_line_total = line_n 520 | cout.check_total = #subs 521 | cout.err_total = #new_selected 522 | 523 | -- 添加错误样式记录文字版 524 | cout.err_styles_str = err_styles_str 525 | return new_selected,cout 526 | end 527 | 528 | -- 标准检测函数 529 | function line_check_default(line) 530 | 531 | return false,line,'' 532 | end 533 | 534 | -- 行尺寸检测 检测行是否超出屏幕显示范围 535 | function line_check_width(meta,style,line) 536 | -- 拷贝当前行到临时变量 537 | temp_line = table.copy(line) 538 | -- 计算整行尺寸 539 | line_math_pos(meta,style,temp_line) 540 | 541 | -- 检测到越界时修改为true 542 | fix_type = false 543 | msg = '' 544 | --[[ 545 | line.left 行的左边缘X坐标,假设其给定对齐,有效边距并且没有碰撞检测 546 | line.center 行中心X坐标,假设其给定对齐,有效边距并且没有碰撞检测 547 | line.right 行的右边X坐标,假设其给定对齐,有效边距并且没有碰撞检测 548 | line.top 行的顶边Y坐标,假设其给定对齐,有效边距并且没有碰撞检测 549 | line.middle 行垂直中心 Y 坐标,假定其给定对齐,有效边距和无碰撞检测 line.vcenter是此的别名 550 | line.bottom 行的下边Y坐标,假设其给定对齐,有效边距并且没有碰撞检测 551 | meta.playresy and meta.playresx 552 | ]] 553 | --debug.var_dump(meta) 554 | --debug.var_dump(temp_line) 555 | 556 | if temp_line.left < 0 or temp_line.left > meta.res_x then 557 | fix_type = true 558 | msg = msg..' 左越界' 559 | end 560 | if temp_line.right > meta.res_x then 561 | fix_type = true 562 | msg = msg..' 右越界' 563 | end 564 | if temp_line.top < 0 or temp_line.top > meta.res_y then 565 | fix_type = true 566 | msg = msg..' 上越界' 567 | end 568 | if temp_line.bottom > meta.res_y then 569 | fix_type = true 570 | msg = msg..' 下越界' 571 | end 572 | if fix_type then 573 | return true,line,msg 574 | end 575 | return false,line,'' 576 | end 577 | 578 | -- 可能会引起压制错误的字符 579 | --[[ 580 | ㊚ 581 | ₂ 582 | ㊤ 583 | ₃ 584 | ㊛ 585 | ㊧ 586 | ㊥ 587 | ₄ 588 | ㊨ 589 | ㊙ 590 | ㊦ 591 | ▦ 592 | ▧ 593 | ㎥ 594 | ▤ 595 | ▥ 596 | ⁴ 597 | ▨ 598 | ▩ 599 | ・ 600 | ♬ 601 | ☞ 602 | ◑ 603 | ₁ 604 | ◐ 605 | ☜ 606 | ▷ 607 | ◁ 608 | ♢ 609 | ♤ 610 | ♧ 611 | ♡ 612 | ▶ 613 | ◀ 614 | ㏘ 615 | ± 616 | ≠ 617 | ≈ 618 | ≡ 619 | < 620 | > 621 | ≤ 622 | ≥ 623 | ∧ 624 | ∨ 625 | ≮ 626 | ≯ 627 | ∑ 628 | ∏ 629 | ∈ 630 | ∩ 631 | ∪ 632 | ⌒ 633 | ∽ 634 | ≌ 635 | ⊙ 636 | √ 637 | ⊥ 638 | ∥∠ 639 | ∫ 640 | ∮ 641 | ∝ 642 | ∞ 643 | · 644 | ∶ 645 | ∵ 646 | ∴ 647 | ∷ 648 | ‰ 649 | ℅ 650 | ¥ 651 | $ 652 | ° 653 | ℃ 654 | ℉ 655 | ′ 656 | ″ 657 | ¢ 658 | 〒 659 | ¤ 660 | ○ 661 | £ 662 | ㏒ 663 | ㏑ 664 | ㏕ 665 | ㎎ 666 | ㎏ 667 | ㎜ 668 | ㎝ 669 | ㎞ 670 | ㏄ 671 | ㎡ 672 | ◇ 673 | ◆ 674 | ■ 675 | □ 676 | ☆ 677 | ○ 678 | △ 679 | ▽ 680 | ★ 681 | ● 682 | ▲ 683 | ▼ 684 | ♠ 685 | ♣ 686 | ♥ 687 | ♀ 688 | ♂ 689 | √ 690 | ✔ 691 | ✘ 692 | × 693 | ♪ 694 | ㈱ 695 | ↔ 696 | ↕ 697 | 卐 698 | 卍 699 | ↖ 700 | ↑ 701 | ↗ 702 | → 703 | ↘ 704 | ↓ 705 | ↙ 706 | ← 707 | ㊣ 708 | 709 | ± ≠ ≈ ≡ < > ≤ ≥ ∧ ∨ ≮ ≯ ∑ ∏ ∈ ∩ ∪ ⌒ ∽ ≌ ⊙ √ ⊥ ∥∠ ∫ ∮ ∝ ∞ · ∶ ∵ ∴ ∷ ‰ ℅ ¥ $ ° ℃ ℉ ′ ″ ¢ 〒 ¤ ○ £ ㏒ ㏑ ㏕ ㎎ ㎏ ㎜ ㎝ ㎞ ㏄ ㎡ ◇ ◆ ■ □ ☆ ○ △ ▽ ★ ● ▲ ▼ ♠ ♣ ♥ ♀ ♂ √ ✔ ✘ × ♪ ㈱ ↔ ↕ 卐 卍 ↖ ↑ ↗ → ↘ ↓ ↙ ← ㊣ 710 | ㊚₂㊤₃㊛㊧㊥₄㊨㊙㊦▦▧㎥▤▥⁴▨▩・♬☞◑₁◐☜▷◁♢♤♧♡▶◀㏘ 711 | ]] 712 | 713 | -- 比较严格的检测模式 714 | local check_char = [[\x{2E80}-\x{9FFF}\sA-Za-z0-9`~!@#$%^&*()_\-\^…+=<>?:"{}|,.\/;'\\[\]·~!~@﹪#¥%^$&**()-_\\—+=⋯{}|•《》?:“”【】、;‘’',。、]] 715 | -- 额外的字符白名单(已经进行过压制测试) 716 | local check_whilelist = [[﹝﹞•¿·︸︷︶︵︿﹀︺︹︽︾﹂﹁﹃﹄︼︻〖〗】【`ˋ¦①②③④⑤⑥⑦⑧⑨⑩㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ± ≠ ≈ ≡ < > ≤ ≥ ∧ ∨ ≮ ≯ ∑ ∏ ∈ ∩ ∪ ⌒ ∽ ≌ ⊙ √ ⊥ ∥∠ ∫ ∮ ∝ ∞ · ∶ ∵ ∴ ∷ ‰ ℅ ¥ $ ° ℃ ℉ ′ ″ ¢ 〒 ¤ ○ £ ㏒ ㏑ ㏕ ㎎ ㎏ ㎜ ㎝ ㎞ ㏄ ㎡ ◇ ◆ ■ □ ☆ ○ △ ▽ ★ ● ▲ ▼ ♠ ♣ ♥ ♀ ♂ √ ✔ ✘ × ♪ ㈱ ↔ ↕ 卐 卍 ↖ ↑ ↗ → ↘ ↓ ↙ ← ㊣]] 717 | 718 | -- 检测并标记任何查找到的可能导致压制错误的行 719 | function line_check_char(line) 720 | -- 忽略存在ASS标签的行之后 721 | -- 检查是否存在可能引起压制错误的字符 722 | result = re.match(line.text, "[^"..check_char..check_whilelist..config.while_errchar.."]+",re.NOSUB) 723 | 724 | if result then 725 | -- 发现了存在可能引起压制错误的行 726 | -- debug.println('解析:') 727 | -- debug.println(line.text) 728 | -- debug.println(check_char) 729 | -- debug.var_dump(result) 730 | msg = '检测到可疑字符 '..result[1].str..' 在第 '..result[1].first..'个字符' 731 | -- debug.var_dump(result) 732 | return true,line,msg 733 | end 734 | 735 | -- 没有问题 736 | return false,line,'' 737 | end 738 | 739 | 740 | -- 60FPS修复 741 | function fix_60fps(subs) 742 | debug.println('60FPS修复:'..'开始修复') 743 | -- 代码来自 Kiriko 的 60FPS修复 744 | for i = 1, #subs do 745 | if subs[i].class == "dialogue" then 746 | local line=subs[i] 747 | if line.start_time%50 == 0 and line.start_time ~= 0 then 748 | line.start_time=line.start_time+10 749 | end 750 | if line.end_time%50 == 0 then 751 | line.end_time=line.end_time+10 752 | end 753 | subs[i]=line 754 | end 755 | end 756 | debug.println('60FPS修复:'..'修复完成') 757 | end 758 | 759 | -- 使用指定样式渲染文本 760 | function text_to_shape(text,style) 761 | if style.class ~= 'style' then return end 762 | text = tostring(text) 763 | FONT_HANDLE = decode.create_font(style.fontname, style.bold, style.italic, style.underline, style.strikeout, style.fontsize) 764 | shape = FONT_HANDLE.text_to_shape(line.text) 765 | return shape 766 | end 767 | 768 | 769 | -- 样式检测 770 | -- 字幕对象解析,解析并检测样式以及检查是否存在未安装字体(字体检测需要Yutils支持) 771 | -- 返回值 bool-是否异常,styles-样式表,meta-元信息表,null_styles_str-未安装字体列表(文本型) 772 | function subs_check_style(subs) 773 | debug.println('样式检测:收集样式及元信息...') 774 | 775 | -- 未安装字体列表(文本型) 776 | null_styles_str = '' 777 | 778 | meta,styles = karaskel_collect_head(subs) 779 | aegisub.progress.set(8) 780 | err_num = 0 781 | debug.println('样式检测:脚本分辨率信息 '..meta.res_x..'x'..meta.res_y) 782 | debug.println('样式检测:收集到 '..#styles..' 个样式') 783 | 784 | if Yutils then 785 | debug.println('样式检测:开始检测未安装字体') 786 | -- 获取系统字体列表 787 | fonts = Yutils.decode.list_fonts(false) 788 | result_type = false 789 | 790 | -- 建立样式的字体表 791 | styles_front = {} 792 | for name,style in pairs(styles) do 793 | if name ~= 'n' then 794 | -- 插入表 795 | styles_front[style.fontname] = {} 796 | styles_front[style.fontname].name = name 797 | end 798 | end 799 | aegisub.progress.set(9) 800 | -- 对字体进行标记 801 | for i = 1,#fonts do 802 | if styles_front[fonts[i].name] then 803 | styles_front[fonts[i].name].check = true 804 | end 805 | end 806 | 807 | -- 输出错误信息(如果存在) 808 | for frontname,style_info in pairs(styles_front) do 809 | if not style_info.check then 810 | -- 这个鬼样式的对应字体不存在 811 | debug.println('样式检测:样式 '..style_info.name..' 的 '..frontname..' 字体未安装') 812 | if null_styles_str == '' then 813 | null_styles_str = frontname..'('..style_info.name..')' 814 | else 815 | null_styles_str = null_styles_str..' , '..frontname..'('..style_info.name..')' 816 | end 817 | err_num = err_num + 1 818 | end 819 | end 820 | 821 | else 822 | debug.println('样式检测:Yutils载入失败,字体检测无法运行') 823 | end 824 | aegisub.progress.set(10) 825 | debug.println('样式检测:检测完毕') 826 | return result_type,styles,meta,err_num,null_styles_str 827 | end 828 | 829 | -- 闪轴与叠轴检测的前置 830 | -- 字幕对象解析,解析并排序,返回排序后的数组(subs_sort_arr) 831 | -- 返回值 subs_sort_arr-排序后的数组,line_start-对话行起始编号 832 | function parse_sort(subs,basic_progress,add_progress) 833 | subs_sort_arr = {} 834 | line_start = 0 835 | -- 编译正则表达式 836 | expr = re.compile([[(\{[\s\S]+?\}){1}]],re.NOSUB) 837 | expr1 = re.compile([[[^\s]{1}]],re.NOSUB) 838 | 839 | if not basic_progress then basic_progress = 0 end 840 | if not add_progress then add_progress = 30 end 841 | 842 | -- 解析忽略非对话行、空行(允许检测空行)、注释行、带特效标签的行、特效不为空的行 843 | for i = 1,#subs do 844 | aegisub.progress.set( i/#subs * add_progress + basic_progress) 845 | line = subs[i] 846 | if line.class == 'dialogue' then 847 | if line_start == 0 then line_start = i - 1 end 848 | if line.text ~= '' then 849 | result = expr:match(line.text) 850 | if not result and not line.comment and line.effect == '' then 851 | table.insert (subs_sort_arr, { 852 | pos = i , 853 | line_id = i - line_start, 854 | start_time = line.start_time , 855 | end_time = line.end_time , 856 | style = line.style 857 | }) 858 | end 859 | end 860 | end 861 | end 862 | 863 | -- 排序函数 864 | local function sort_comp(element1, elemnet2) 865 | if element1 == nil then 866 | return false; 867 | end 868 | if elemnet2 == nil then 869 | return true; 870 | end 871 | return element1.start_time < elemnet2.start_time 872 | end 873 | table.sort(subs_sort_arr,sort_comp) 874 | 875 | return subs_sort_arr 876 | end 877 | 878 | -- 闪轴检测,参数 subs_sort_arr(parse_sort函数返回值),subs(存在时检测并修复闪轴) 879 | -- 返回值 新选择行 880 | function check_interval200(subs_sort_arr,subs) 881 | --[[ 882 | { 883 | pos = i , 884 | start_time = line.start_time , 885 | end_time = line.end_time , 886 | style = line.style 887 | } 888 | ]] 889 | 890 | -- 缓存每个样式的前一行数据 891 | style_chace = {} 892 | -- 选择行 893 | new_selected = {} 894 | -- 选择限制行 895 | limit_add = {} 896 | for i,tl in ipairs(subs_sort_arr) do 897 | if style_chace[tl.style] then 898 | interva = tl.start_time - style_chace[tl.style].end_time 899 | if interva < 200 and interva > 0 then 900 | -- <200ms判断为闪轴 901 | -- 添加检测到的闪轴到新选择行 902 | -- 添加历史行(规避重复) 903 | if not limit_add[style_chace[tl.style].pos] then 904 | table.insert(new_selected,style_chace[tl.style].pos) 905 | end 906 | 907 | -- 添加当前行 908 | table.insert(new_selected,tl.pos) 909 | -- 标识当前行为选择行 910 | limit_add[style_chace[tl.style].pos] = true 911 | 912 | if subs then 913 | -- 如果字幕对象存在,就应该整活了 914 | -- 每行起始时间不变,上一行结束时间向后调至紧贴下一行起始时间 915 | pre_line = subs[style_chace[tl.style].pos] 916 | line = subs[tl.pos] 917 | pre_line.end_time = line.start_time 918 | subs[style_chace[tl.style].pos] = pre_line 919 | end 920 | 921 | end 922 | end 923 | style_chace[tl.style] = tl 924 | end 925 | 926 | return new_selected 927 | end 928 | 929 | -- 叠轴、灵异轴检测,参数 subs_sort_arr(parse_sort函数返回值) 930 | -- 返回值 新选择行 931 | function check_overlap(subs_sort_arr,new_selected,basic_progress,add_progress) 932 | -- 无法自动修复,这种怪操作要挨锤的 933 | -- 缓存每个样式的前一行数据 934 | style_chace = {} 935 | -- 选择行 936 | if not new_selected then new_selected = {} end 937 | if not basic_progress then basic_progress = 30 end 938 | if not add_progress then add_progress = 70 end 939 | for i,tl in ipairs(subs_sort_arr) do 940 | aegisub.progress.set( i/#subs_sort_arr * add_progress + basic_progress) 941 | if style_chace[tl.style] then 942 | -- 同一样式 本行的开始时间小于上一行的结束时间 (单人这种轴太怪了,多人同一个样式也挺怪的) 943 | if tl.start_time < style_chace[tl.style].end_time then 944 | debug.println("叠轴检测:".."第"..tl.line_id.."行发现叠轴") 945 | table.insert(new_selected,tl.pos) 946 | end 947 | -- 一行的开始时间大于结束时间的畸形种 948 | if tl.start_time > tl.end_time then 949 | aegisub.debug.out(0, "叠轴检测:".."第"..tl.line_id.."行发现灵异轴") 950 | table.insert(new_selected,tl.pos) 951 | end 952 | end 953 | style_chace[tl.style] = tl 954 | end 955 | return new_selected 956 | end 957 | 958 | -- 检测开始函数,正常规范三参数+检测限制参数 959 | function check_start(subs, selected_lines, active_line, select_check) 960 | --复查助手 961 | 962 | select_check_def = { 963 | fix_fps = true, 964 | size = true,-- 尺寸异常检测 965 | error_char = true, -- 乱码字符检测 966 | style = true, -- 样式不存在检测 967 | overlap = true -- 叠轴检测 968 | } 969 | if not select_check then 970 | select_check = select_check_def 971 | end 972 | 973 | aegisub.progress.task('复 查 助 手') 974 | aegisub.progress.set(0) 975 | --[[ 976 | if #subs > 10000 then 977 | debug.println('复查助手:检测到字幕行行数超过一万..') 978 | if not display.confirm('检测到行数超过一万行\n是否继续?\n继续运行,运行时间可能较长',1) then 979 | aegisub.progress.task0('复查助手运行结束') 980 | debug.println('自动复查:选择停止') 981 | aegisub.cancel() 982 | end 983 | debug.println('复查助手:继续运行...') 984 | end 985 | ]] 986 | 987 | msg = [[小助手提醒您: 988 | 1.智能60FPS修复(视频已打开)(自动) 989 | 2.识别可能导致压制乱码的字符 990 | 3.识别单行字幕过长(视频已打开) 991 | 4.识别不存在的样式 992 | 5.同样式非注释行重叠 993 | 注:文件名也可能导致压制乱码,请自行检查 994 | 注:注释、说话人或特效不为空的行将被忽略 995 | 确定后开始自动复查!]] 996 | debug.println(msg..'\n') 997 | if not display.confirm(msg,1) then 998 | aegisub.progress.task('复查助手运行结束') 999 | debug.println('复查助手:选择停止') 1000 | aegisub.cancel() 1001 | end 1002 | 1003 | debug.println('复查助手:检查开始\n') 1004 | aegisub.progress.set(1) 1005 | -- 智能60FPS修复 1006 | aegisub.progress.task('智能60FPS修复') 1007 | debug.println('智能60FPS修复...') 1008 | if select_check.fix_60fps ~= false then 1009 | -- 判断前10s的帧数(视频需要至少10S长...) 1010 | frame = aegisub.frame_from_ms(10000) 1011 | if frame and frame >= 590 and frame <= 610 then 1012 | debug.println('智能60FPS修复:'..'判断为60FPS') 1013 | -- 这种情况就可以判定为60FPS了 1014 | fix_60fps(subs) 1015 | else 1016 | debug.println('智能60FPS修复:'..'视频不为60FPS或视频未打开,跳过修复') 1017 | end 1018 | else 1019 | debug.println('智能60FPS修复:'..'跳过修复') 1020 | end 1021 | 1022 | aegisub.progress.set(5) 1023 | --debug.println('智能60FPS修复执行完毕...') 1024 | debug.println() 1025 | 1026 | 1027 | -- 样式检测 1028 | aegisub.progress.task('样式字体检测...') 1029 | style_check,styles,meta,style_check_err_num,style_check_str = subs_check_style(subs) 1030 | debug.println() 1031 | 1032 | aegisub.progress.set(10) 1033 | -- 行错误检测 1034 | aegisub.progress.task('行错误检测') 1035 | debug.println('行错误检测...') 1036 | new_selected,check_line_cout = subs_check_line(subs,styles,meta,select_check) 1037 | --debug.println('行错误检测执行完毕...') 1038 | 1039 | aegisub.progress.set(90) 1040 | aegisub.progress.task('叠轴检测') 1041 | overlap_line_n = 0 1042 | if select_check.overlap then 1043 | -- 解析 1044 | subs_sort_arr = parse_sort(subs,90,3) 1045 | aegisub.progress.set(93) 1046 | -- 检测行(更新选择) 1047 | chace_ns = #new_selected 1048 | new_selected = check_overlap(subs_sort_arr,new_selected,93,7) 1049 | 1050 | -- 叠轴行数 1051 | overlap_line_n = #new_selected - chace_ns 1052 | end 1053 | 1054 | 1055 | aegisub.progress.set(100) 1056 | debug.println() 1057 | aegisub.progress.task('复查助手运行结束') 1058 | debug.println('====复查助手·统计====') 1059 | if style_check or #new_selected ~= 0 or style_check_err_num ~= 0 then 1060 | debug.println('?:这ASS怪怪的') 1061 | debug.println('实际检测行:'..check_line_cout.check_line_total-check_line_cout.ignore_total) 1062 | if Yutils then 1063 | debug.println('字体未安装:'..style_check_err_num) 1064 | if style_check_err_num ~= 0 then 1065 | debug.println('未安装字体(所属样式):'..style_check_str) 1066 | end 1067 | else 1068 | debug.println('未安装字体检测:警告,未安装Yutils,检测无法运行。') 1069 | end 1070 | if select_check.overlap then 1071 | if overlap_line_n ~= 0 then 1072 | debug.println('!!!警告,当前叠轴行数不为0,建议仔细检查!!!') 1073 | end 1074 | debug.println('叠轴行数:'..overlap_line_n) 1075 | end 1076 | debug.println('异常对话行总数(不计叠轴):'..check_line_cout.err_total) 1077 | if check_line_cout.err_total ~= 0 then 1078 | if select_check.size then 1079 | debug.println('尺寸过大的行:'..check_line_cout.err_size) 1080 | end 1081 | if select_check.error_char then 1082 | debug.println('存在可疑字符的行:'..check_line_cout.err_char) 1083 | end 1084 | if select_check.style then 1085 | debug.println('使用不存在样式的行:'..check_line_cout.err_style) 1086 | if check_line_cout.err_style ~= 0 then 1087 | debug.println('不存在的样式:'..cout.err_styles_str) 1088 | end 1089 | end 1090 | end 1091 | debug.println('所有检测到的异常行已经标记\n关闭窗口后显示标记结果') 1092 | else 1093 | debug.println('所有检测执行完毕') 1094 | debug.println('总识别行:'..#subs) 1095 | debug.println('总检测行:'..check_line_cout.check_line_total-check_line_cout.ignore_total) 1096 | if not Yutils then 1097 | debug.println('未安装字体检测:警告,未安装Yutils,检测无法运行。') 1098 | end 1099 | debug.println('特效不为空的行:'..check_line_cout.dialogue_effect_total) 1100 | debug.println('忽略检测行总数:'..check_line_cout.ignore_total) 1101 | debug.println('说话人不为空的行:'..check_line_cout.dialogue_actor_total) 1102 | debug.println('这个ASS看起来没什么不对(') 1103 | end 1104 | 1105 | 1106 | if #new_selected == 0 then 1107 | return 1108 | else 1109 | return new_selected 1110 | end 1111 | end 1112 | 1113 | 1114 | 1115 | -- 菜单的选择启动函数 1116 | 1117 | function macro_main(subs, selected_lines, active_line) 1118 | -- 修改全局提示等级 1119 | debug.changelevel(3) 1120 | -- 默认全都检查 1121 | return check_start(subs, selected_lines, active_line) 1122 | end 1123 | 1124 | function macro_select_sizeover(subs, selected_lines, active_line) 1125 | -- 你这行,太长了吧? 1126 | -- 修改全局提示等级 1127 | debug.changelevel(4) 1128 | select_check = { 1129 | size = true,-- 尺寸异常检测 1130 | error_char = false, -- 乱码字符检测 1131 | style = false, -- 样式不存在检测 1132 | overlap = false -- 叠轴检测 1133 | } 1134 | -- 检查限制 1135 | return check_start(subs, selected_lines, active_line, select_check) 1136 | end 1137 | 1138 | function macro_select_errchar(subs, selected_lines, active_line) 1139 | -- zai?为什么用特殊字符,还是这种特殊字符? 1140 | -- 修改全局提示等级 1141 | debug.changelevel(3) 1142 | debug.println('乱码检测允许的特殊字符(常见特殊字符本机测试无害):'..check_whilelist.."\n") 1143 | select_check = { 1144 | size = false,-- 尺寸异常检测 1145 | error_char = true, -- 乱码字符检测 1146 | style = false, -- 样式不存在检测 1147 | overlap = false -- 叠轴检测 1148 | } 1149 | -- 检查限制 1150 | return check_start(subs, selected_lines, active_line, select_check) 1151 | end 1152 | 1153 | function macro_select_nullstyle(subs, selected_lines, active_line) 1154 | -- 这样式咋空了 1155 | -- 修改全局提示等级 1156 | debug.changelevel(3) 1157 | select_check = { 1158 | size = false,-- 尺寸异常检测 1159 | error_char = false, -- 乱码字符检测 1160 | style = true, -- 样式不存在检测 1161 | overlap = false -- 叠轴检测 1162 | } 1163 | -- 检查限制 1164 | return check_start(subs, selected_lines, active_line, select_check) 1165 | end 1166 | 1167 | function macro_select_basic(subs, selected_lines, active_line) 1168 | -- 最基础的检查 1169 | -- 修改全局提示等级 1170 | debug.changelevel(3) 1171 | select_check = { 1172 | size = false,-- 尺寸异常检测 1173 | error_char = false, -- 乱码字符检测 1174 | style = false, -- 样式不存在检测 1175 | overlap = false -- 叠轴检测 1176 | } 1177 | -- 检查限制 1178 | return check_start(subs, selected_lines, active_line, select_check) 1179 | end 1180 | 1181 | function macro_interval200(subs, selected_lines, active_line) 1182 | -- 检查行间隔是否<200ms 1183 | -- 修改全局提示等级 1184 | debug.changelevel(4) 1185 | -- 解析 1186 | subs_sort_arr = parse_sort(subs) 1187 | -- 判断行 1188 | new_selected = check_interval200(subs_sort_arr) 1189 | if #new_selected ~= 0 then 1190 | return new_selected 1191 | end 1192 | display.confirm('未发现间隔小于200ms的行',0) 1193 | end 1194 | 1195 | function macro_interval200_fix(subs, selected_lines, active_line) 1196 | -- 检查行间隔是否<200ms 1197 | -- 修改全局提示等级 1198 | debug.changelevel(4) 1199 | msg = '是否确认进行修复?' 1200 | debug.println(msg..'\n') 1201 | if not display.confirm(msg,1) then 1202 | aegisub.progress.task('复查助手运行结束') 1203 | debug.println('复查助手:选择停止') 1204 | aegisub.cancel() 1205 | end 1206 | 1207 | 1208 | -- 解析 1209 | subs_sort_arr = parse_sort(subs) 1210 | -- 判断行 1211 | new_selected = check_interval200(subs_sort_arr,subs) 1212 | 1213 | if #new_selected ~= 0 then 1214 | return new_selected 1215 | end 1216 | display.confirm('未发现间隔小于200ms的行',0) 1217 | end 1218 | 1219 | function macro_select_overlap(subs, selected_lines, active_line) 1220 | aegisub.progress.task('复 查 助 手') 1221 | -- 兄啊,同一个人说话怎么叠一起的? 1222 | -- 修改全局提示等级 1223 | debug.changelevel(3) 1224 | msg = [[小助手提醒您: 1225 | 1.智能60FPS修复(视频已打开)(自动) 1226 | 2.识别可能导致压制乱码的字符 1227 | 3.识别单行字幕过长(视频已打开) 1228 | 4.识别不存在的样式 1229 | 5.同样式非注释行重叠 1230 | 注:文件名也可能导致压制乱码,请自行检查 1231 | 注:注释、说话人或特效不为空的行将被忽略]] 1232 | debug.println(msg..'\n') 1233 | aegisub.progress.set(0) 1234 | -- 解析 1235 | subs_sort_arr = parse_sort(subs) 1236 | aegisub.progress.set(30) 1237 | -- 检测行 1238 | new_selected = check_overlap(subs_sort_arr) 1239 | aegisub.progress.set(100) 1240 | debug.println() 1241 | debug.println() 1242 | debug.println('====复查助手·统计====') 1243 | if #new_selected ~= 0 then 1244 | debug.println('如无意外,此轴可锤') 1245 | debug.println('叠轴计数:'..#new_selected) 1246 | return new_selected 1247 | end 1248 | debug.println('看起来没有叠轴的存在呢') 1249 | display.confirm('未发现可能存在的叠轴',0) 1250 | end 1251 | 1252 | function macro_fix60fps(subs, selected_lines, active_line) 1253 | debug.changelevel(4) 1254 | fix_60fps(subs) 1255 | display.confirm('我跟你说,它 好 了'.."\n"..'*此功能重复使用无影响',0) 1256 | end 1257 | 1258 | local str_trim_expr_pre = re.compile([[^\s+]],re.NOSUB) 1259 | function macro_remove_headblank(subs) 1260 | new_selected = {} 1261 | for i=1,#subs do 1262 | if subs[i].class == 'dialogue' then 1263 | -- 解析忽略非对话行、空行(允许检测空行)、注释行、带特效标签的行、特效不为空的行 1264 | expr = re.compile([[(\{[\s\S]+?\}){1}]],re.NOSUB) 1265 | expr1 = re.compile([[[^\s]{1}]],re.NOSUB) 1266 | line = subs[i] 1267 | if line.class == 'dialogue' then 1268 | if line.text ~= '' then 1269 | result = expr:match(line.text) 1270 | if not result and not line.comment and line.effect == '' then 1271 | line.text, rep_count = str_trim_expr_pre:sub(line.text,'') 1272 | if line.text ~= nil and line.text ~= subs[i].text then 1273 | subs[i] = line 1274 | table.insert(new_selected,i) 1275 | end 1276 | end 1277 | end 1278 | end 1279 | end 1280 | end 1281 | if #new_selected ~= 0 then 1282 | return new_selected 1283 | end 1284 | return out_str, rep_count 1285 | end 1286 | 1287 | function macro_select_overtimes(subs) 1288 | new_selected = {} 1289 | for i=1,#subs do 1290 | if subs[i].class == 'dialogue' then 1291 | -- 解析忽略非对话行、空行(允许检测空行)、注释行、带特效标签的行、特效不为空的行 1292 | expr = re.compile([[(\{[\s\S]+?\}){1}]],re.NOSUB) 1293 | expr1 = re.compile([[[^\s]{1}]],re.NOSUB) 1294 | line = subs[i] 1295 | if line.class == 'dialogue' then 1296 | if line.text ~= '' then 1297 | result = expr:match(line.text) 1298 | if not result and not line.comment and line.effect == '' then 1299 | if subs[i].end_time - subs[i].start_time > 5000 then 1300 | table.insert(new_selected,i) 1301 | end 1302 | end 1303 | end 1304 | end 1305 | end 1306 | end 1307 | if #new_selected ~= 0 then 1308 | return new_selected 1309 | end 1310 | end 1311 | 1312 | function macro_about() 1313 | -- 修改全局提示等级 1314 | debug.changelevel(1) 1315 | version_log = [[ 1316 | 1.2.0 Alpha 2019-12-20 1317 | ·更新了特殊符号检测算法,可以检测常用字符以外的符号。 1318 | ·检测所有除了中日韩统一表意文字(CJK Unified Ideographs)之外的特殊字符。 1319 | 1320 | 1.3.0 Alpha 1321 | ·修复了空轴无法检测闪轴的漏洞 1322 | ·更新了压制特殊符号检测算法,采用白名单制,仅白名单字符可通过。 1323 | ·注:白名单字符可通过自行编辑lua文件进行定义。 1324 | ·增加了关于里更新日志的显示 1325 | 1326 | 1.3.1 Alpha 1327 | ·增加两个小功能,移除每行行首空格,标记超过5S的轴 1328 | 1329 | 1.3.2 Alpha 1330 | ·修复有一个功能没做的BUG 1331 | 1332 | ↑更新日志↑ 1333 | ]] 1334 | msg = version_log.."\n\n"..'乱码检测允许的特殊字符:'..check_whilelist.."\n\n"..'复查小助手 '..script_version.."\n".. 1335 | [[ 1336 | 本助手的功能有 1337 | 1.智能60FPS修复(视频已打开)(自动) 1338 | 2.识别可能导致压制乱码的字符 1339 | 3.识别单行字幕过长(视频已打开) 1340 | 4.识别不存在的样式 1341 | 5.同样式非注释行重叠以及包含 1342 | 6.闪轴检测及修复(行间隔<200ms) 1343 | 注:60FPS修复经过测试多次使用对ASS无负面影响 1344 | 注:闪轴检测为独立功能,仅在菜单内提供,不自动使用,并且提供自动修复选项。 1345 | 注:文件名也可能导致压制乱码,请自行检查 1346 | 注:注释、说话人或特效不为空的行将被忽略 1347 | 注:本助手的提示等级为level 3 如果不能正常显示信息或者其他异常请检查您的设置 1348 | 注:本插件所做修改可由AEG的撤销功能撤回 1349 | 作者:晨轩°(QQ3309003591) 1350 | 本关于的最后修改时间:2020年1月20日 1351 | 感谢您的使用! 1352 | ]] 1353 | debug.println(msg) 1354 | end 1355 | 1356 | 1357 | 1358 | 1359 | -- 注册AEG菜单 1360 | 1361 | aegisub.register_macro(script_name, script_description, macro_main, macro_can_use) 1362 | 1363 | aegisub.register_macro(script_name.."-菜单".."/基本检查", "查查更健康", macro_select_basic, macro_can_use) 1364 | 1365 | aegisub.register_macro(script_name.."-菜单".."/独立检测/选择尺寸过大的行", "太长是不好的", macro_select_sizeover, macro_can_use) 1366 | aegisub.register_macro(script_name.."-菜单".."/独立检测/选择含可疑字符行", "可能引起压制错误的行", macro_select_errchar, macro_can_use) 1367 | aegisub.register_macro(script_name.."-菜单".."/独立检测/检测不存在的样式", "兄啊,你这有点不对劲啊", macro_select_nullstyle, macro_can_use) 1368 | aegisub.register_macro(script_name.."-菜单".."/独立检测/检测叠轴", "兄啊,同一个人说话怎么叠一起的?", macro_select_overlap, macro_can_use) 1369 | 1370 | aegisub.register_macro(script_name.."-菜单".."/小工具/移除行首空格", "轴的空间还蛮大的", macro_remove_headblank, macro_can_use) 1371 | aegisub.register_macro(script_name.."-菜单".."/小工具/标记超过5s的轴", "轴的时间还蛮长的", macro_select_overtimes, macro_can_use) 1372 | aegisub.register_macro(script_name.."-菜单".."/检测字幕闪现(行间隔<200ms)", "这ass费眼睛", macro_interval200, macro_can_use) 1373 | aegisub.register_macro(script_name.."-菜单".."/修复字幕闪现(行间隔<200ms)", "它不会费眼睛了", macro_interval200_fix, macro_can_use) 1374 | aegisub.register_macro(script_name.."-菜单".."/60fps修复", "这个60FPS的视频看起来中暑了,不如我们...", macro_fix60fps, macro_can_use) 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | aegisub.register_macro(script_name.."-菜单".."/关于", "一些说明", macro_about, macro_can_use) -------------------------------------------------------------------------------- /复查助手-已发布版本存档/AEG复查小助手v1.3.3 正式版[2019年1月29日]-包含安装与使用说明 需解压.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/AEG复查小助手v1.3.3 正式版[2019年1月29日]-包含安装与使用说明 需解压.zip -------------------------------------------------------------------------------- /复查助手-已发布版本存档/AEG复查小助手v1.3.4 正式版[2020年4月7日]-包含安装与使用说明 需解压.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/AEG复查小助手v1.3.4 正式版[2020年4月7日]-包含安装与使用说明 需解压.zip -------------------------------------------------------------------------------- /复查助手-已发布版本存档/README.md: -------------------------------------------------------------------------------- 1 | # 复查小助手 2 | 原本打算改名的,想想还是这个名字亲切.jpg 3 | 4 | 复查小助手距离最初开发的第一个测试版本已经一年多了 5 | 6 | 2021年1月29日是复查小助手正式版一周年的日子。 7 | 8 | 所以开发~~重写~~了复查小助手年度更新版 V2.0.0! 9 | 10 | [更新说明](https://github.com/chenxuan353/Lua_Aegisub32/blob/master/%E5%A4%8D%E6%9F%A5%E5%8A%A9%E6%89%8B-%E5%B7%B2%E5%8F%91%E5%B8%83%E7%89%88%E6%9C%AC%E5%AD%98%E6%A1%A3/%E6%9B%B4%E6%96%B0%E7%AE%80%E8%BF%B0.md) 11 | 12 | 13 | 14 | ## 功能 15 | 16 | 支持多功能可配置的轴文件检查 17 | 18 | 1. 智能60FPS修复(视频已打开) 19 | 20 | 2. 识别可能导致压制乱码的字符(包含检查当前已打开文件名) 21 | 22 | 3. 识别单行字幕过长(超出视频边界) 23 | 24 | 4. 识别不存在的样式 25 | 26 | 5. 同样式非注释行重叠以及包含 27 | 28 | 6. 识别未安装的字体(需要Yutils支持) 29 | 30 | 7. 闪轴检测及修复(比旧版更好的检测联动轴) 31 | 32 | 8. 检查格式,及格式化(自定义检查与替换规则,支持分样式设置) 33 | 34 | -------------------------------------------------------------------------------- /复查助手-已发布版本存档/[复查小助手]使用说明v2.0.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/[复查小助手]使用说明v2.0.0.pdf -------------------------------------------------------------------------------- /复查助手-已发布版本存档/[复查小助手年度更新]查轴助手v1.0.0dev.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/[复查小助手年度更新]查轴助手v1.0.0dev.zip -------------------------------------------------------------------------------- /复查助手-已发布版本存档/[年度]AEG复查小助手v2.0.0 正式版[2021年1月27日]-包含安装与使用说明 需解压.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/[年度]AEG复查小助手v2.0.0 正式版[2021年1月27日]-包含安装与使用说明 需解压.zip -------------------------------------------------------------------------------- /复查助手-已发布版本存档/[年度]AEG复查小助手v2.0.1 正式版[2021年1月27日]-包含安装与使用说明 需解压.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/[年度]AEG复查小助手v2.0.1 正式版[2021年1月27日]-包含安装与使用说明 需解压.zip -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/AEG自动化辅助装载器 - 自动化 脚本管理 加载.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 本脚本使用最基础的AEG函数装载 3 | 注:必须安装AEG辅助函数库方可正常运行 4 | 大致流程 5 | 1、测试函数库是否装载 6 | 2、如果使用自动装载,在未装载时测试权限 7 | 3、没有权限则提示使用管理员权限打开,若有权限则进行下一步 8 | 4、通过文件加载器加载指定文件到指定位置 9 | 注:如果不使用自动装载将提供打开文件夹的功能 10 | ]] 11 | -- 脚本名 12 | script_name = "AEG自动化辅助装载器" 13 | -- 脚本描述 14 | script_description = "用于辅助AEG插件、库、dll等文件的装载" 15 | -- 作者 16 | script_author = "晨轩°" 17 | 18 | -- CX插件扩展值 19 | -- 脚本签名(同一脚本签名请保持不变,签名不能含特殊字符,防止配置冲突) 20 | script_signature = "com.chenxuan.辅助装载" 21 | -- 版本号 22 | script_version = "1.0.0dev" 23 | -- 关于 24 | script_about = [[ 25 | 用于辅助AEG插件、库、dll等文件的装载 26 | ]] 27 | -- 更新日志 28 | script_ChangeLog = [[ 29 | 没有日志~ 30 | ]] 31 | 32 | lfs = require("lfs") 33 | 34 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 35 | select_file_last_select_path = "" 36 | function select_file(title, wildcards, default_dir, must_exist, default_file, allow_multiple) 37 | if title == nil then title = '选择文件' end 38 | if default_file == nil then default_file = '' end 39 | if default_dir == nil then 40 | default_dir = '' 41 | if select_file_last_select_path then 42 | default_dir = select_file_last_select_path 43 | else 44 | temp_path = aegisub.decode_path('?script\\file') 45 | if temp_path ~= '?script\\file' then 46 | default_dir = temp_path 47 | end 48 | end 49 | end 50 | if wildcards == nil then wildcards = '所有文件(*)|*' end 51 | if allow_multiple == nil then allow_multiple = false end 52 | if must_exist == nil then must_exist = true end 53 | file_name = aegisub.dialog.open(title, default_file, default_dir, wildcards, allow_multiple, must_exist) 54 | if file_name then select_file_last_select_path = file_name end 55 | return file_name 56 | end 57 | -- 二进制复制文件 58 | function copyFile(sourcePath,targetPath) 59 | local rf = io.open(sourcePath,"rb") --使用“rb”打开二进制文件,如果是“r”的话,是使用文本方式打开,遇到‘0’时会结束读取 60 | local len = rf:seek("end") --获取文件长度 61 | local wf = io.open(targetPath,"wb") --用“wb”方法写入二进制文件 62 | if len ~= 0 then 63 | rf:seek("set",0)--重新设置文件索引为0的位置 64 | local data = rf:read(len) --根据文件长度读取文件数据 65 | wf:write(data) 66 | end 67 | rf:close() 68 | wf:close() 69 | 70 | end 71 | 72 | 73 | -- 环境错误检测 74 | local cx_help_libname = "CX_AEG插件辅助函数库" 75 | local cx_help = nil 76 | if (not pcall( 77 | function(libname) 78 | cx_help = require(cx_help_libname) 79 | end 80 | ,cx_help_libname 81 | )) or cx_help == nil 82 | then 83 | local function errLoadLibDealFunc(subs) 84 | local function print(msg) 85 | aegisub.debug.out(0, msg) 86 | end 87 | if cx_help ~= nil then 88 | cx_help.alert("辅助库已安装\n请重新载入脚本或重启Aegisub使改动生效") 89 | return 90 | end 91 | filename = select_file("选择CX_AEG插件辅助函数库文件","lua(*.lua)|*.lua") 92 | if filename == nil or filename == "" then 93 | print("未选择合法文件") 94 | return 95 | end 96 | print("已确认库文件路径:"..filename.."\n") 97 | print("正在检测脚本合法性...\n") 98 | -- 备份原始档案,覆盖环境执行测试 99 | local cachepath = package.path 100 | local cache_aeg = aegisub 101 | local cache_aegmin = aeg 102 | local supertable = {} 103 | --[[ 104 | setmetatable(supertable,{ 105 | __index = function(mytable, key) 106 | return mytable 107 | end 108 | ,__newindex = function(mytable, key, value) 109 | return 110 | end 111 | ,__call = function(mytable, newtable) 112 | return mytable 113 | end 114 | }) 115 | ]] 116 | if pcall( 117 | function(filename) 118 | -- 测试加载文件 119 | local fi = string.find(string.reverse(filename),"\\") 120 | package.path = package.path..";"..string.sub(filename,0,-fi).."/?.lua" 121 | local modname = string.sub(string.sub(filename,-fi + 1),0,-5) 122 | local testload = require(modname) 123 | assert(testload.aeg.exit == aegisub.cancel,"非法的辅助库文件") 124 | end, filename) 125 | then 126 | _G.aegisub = cache_aeg 127 | _G.aeg = cache_aegmin 128 | package.path = cachepath 129 | print(">>>脚本测试通过<<<\n") 130 | else 131 | _G.aegisub = cache_aeg 132 | _G.aeg = cache_aegmin 133 | package.path = cachepath 134 | print("!!!脚本测试不通过,无法执行安装!!!\n") 135 | return 136 | end 137 | libpath = aegisub.decode_path('?data\\').."automation\\include\\" 138 | print("安装路径:"..libpath.."\n") 139 | print("尝试安装...\n") 140 | if pcall(copyFile,filename,libpath..cx_help_libname..".lua") 141 | then 142 | print(">>>安装成功!<<<\n") 143 | else 144 | print("!!!脚本安装失败,可能权限不足,请尝试管理员权限打开Aegisub!!!\n") 145 | return 146 | end 147 | if (not pcall( 148 | function(libname) 149 | cx_help = require(cx_help_libname) 150 | end 151 | ,cx_help_libname 152 | )) or cx_help == nil 153 | then 154 | print("!!!未知原因错误,无法载入库文件!!!") 155 | return 156 | end 157 | print("函数辅助库文件加载成功!请重新加载脚本或者重启Aegisub以应用修改\n") 158 | cx_help.alert("安装完成,欢迎使用!") 159 | end 160 | aegisub.register_macro("自动装载 - 未初始化","装载器未初始化完成",errLoadLibDealFunc) 161 | 162 | 163 | -- 未成功加载库的返回 164 | return 165 | end 166 | 167 | 168 | 169 | -- 创建合适的函数环境 170 | cx_help.table.merge(_G,cx_help) 171 | setting.setLevel(0) 172 | function file_exists(path) 173 | local file = io.open(path, "rb") 174 | if file then file:close() end 175 | return file ~= nil 176 | end 177 | function loadlib() 178 | filename = select_file("选择库文件","lua(*.lua)|*.lua") 179 | if filename == nil or filename == "" then 180 | return 181 | end 182 | local fi = string.find(string.reverse(filename),"\\") 183 | 184 | if not confirm("你确定要安装 "..string.sub(string.sub(filename,-fi + 1),0,-5).." 吗?") then 185 | return 186 | end 187 | print("已确认库文件路径:"..filename.."\n") 188 | print("正在测试脚本合法性...\n") 189 | -- 备份原始档案,覆盖环境执行测试 190 | local cachepath = package.path 191 | local cache_aeg = aegisub 192 | local cache_aegmin = aeg 193 | local supertable = {} 194 | --[[ 195 | setmetatable(supertable,{ 196 | __index = function(mytable, key) 197 | return mytable 198 | end 199 | ,__newindex = function(mytable, key, value) 200 | return 201 | end 202 | ,__call = function(mytable, newtable) 203 | return mytable 204 | end 205 | }) 206 | ]] 207 | _G.aegisub = supertable 208 | _G.aeg = supertable 209 | cx_help.aeg = supertable 210 | if pcall( 211 | function(filename) 212 | -- 测试加载文件 213 | local fi = string.find(string.reverse(filename),"\\") 214 | package.path = package.path..";"..string.sub(filename,0,-fi).."/?.lua" 215 | local modname = string.sub(string.sub(filename,-fi + 1),0,-5) 216 | local testload = require(modname) 217 | end, filename) 218 | then 219 | _G.aegisub = cache_aeg 220 | _G.aeg = cache_aegmin 221 | cx_help.aeg = cache_aegmin 222 | package.path = cachepath 223 | print(">>>脚本测试通过<<<\n") 224 | else 225 | _G.aegisub = cache_aeg 226 | _G.aeg = cache_aegmin 227 | cx_help.aeg = cache_aegmin 228 | package.path = cachepath 229 | print("!!!脚本测试不通过,无法执行安装!!!\n") 230 | return 231 | end 232 | 233 | libpath = aeg.path.global_lib 234 | print("安装路径:"..libpath.."\n") 235 | print("尝试安装...\n") 236 | 237 | local targetPath = libpath..string.sub(filename,-fi + 1) 238 | if file_exists(targetPath) then 239 | if not confirm("文件已存在,是否覆盖?") then 240 | print("已取消安装...\n") 241 | return 242 | end 243 | end 244 | if pcall(copyFile,filename,targetPath) 245 | then 246 | print(">>>安装成功!<<<\n") 247 | else 248 | print("!!!脚本安装失败,可能权限不足,请尝试管理员权限打开Aegisub!!!\n") 249 | return 250 | end 251 | print("请重新加载脚本或者重启Aegisub以应用修改\n") 252 | end 253 | 254 | function os_openpath(path) 255 | os.execute("explorer "..path) 256 | end 257 | 258 | -- 无错误导入(参数 库名),出错返回nil 259 | function noerrRequire(libname) 260 | local res = nil 261 | pcall( 262 | function (reqname) 263 | -- 测试加载文件 264 | res = require(reqname) 265 | end, libname) 266 | return res 267 | end 268 | 269 | -- 载入测试 270 | local Yutils = noerrRequire("Yutils") 271 | 272 | -- 环境检测菜单 273 | envTestMenu = { 274 | menus.menu("辅助函数库",nil,function() end,nil,function () return true end) 275 | ,menus.menu("Yutils",nil,function() end,nil,function () return Yutils ~= nil end) 276 | 277 | } 278 | 279 | function loadscript() 280 | filename = select_file("选择脚本文件","lua(*.lua)|*.lua") 281 | if filename == nil or filename == "" then 282 | return 283 | end 284 | local fi = string.find(string.reverse(filename),"\\") 285 | if not confirm("你确定要安装 "..string.sub(string.sub(filename,-fi + 1),0,-5).." 吗?") then 286 | return 287 | end 288 | print("已确认脚本文件路径:"..filename.."\n") 289 | 290 | libpath = aeg.path.global_script 291 | print("安装路径:"..libpath.."\n") 292 | print("尝试安装...\n") 293 | 294 | local targetPath = libpath..string.sub(filename,-fi + 1) 295 | if file_exists(targetPath) then 296 | if not confirm("文件已存在,是否覆盖?") then 297 | print("已取消安装...\n") 298 | return 299 | end 300 | end 301 | if pcall(copyFile,filename,targetPath) 302 | then 303 | print(">>>安装成功!<<<\n") 304 | else 305 | print("!!!脚本安装失败,可能权限不足,请尝试管理员权限打开Aegisub!!!\n") 306 | return 307 | end 308 | end 309 | 310 | function removelib() 311 | -- 遍历脚本目录 312 | local scriptpath = aeg.path.global_lib 313 | local llist = {} 314 | for path in lfs.dir(scriptpath) do 315 | if string.sub(path,-4) == ".lua" then 316 | local unit = string.sub(path,0,-5) 317 | table.insert(llist,unit) 318 | end 319 | end 320 | config = { 321 | {class="label",label ="库列表",x=0,y=0,width = 5,height = 1} 322 | ,{class = "dropdown",value = "",items = llist,name = "dr",hint="选择需要卸载的插件",x=0,y=1,width = 5,height = 1} 323 | } 324 | -- 显示对话框 325 | btn, btnresult = aegisub.dialog.display(config) 326 | removename = btnresult.dr 327 | if btn == false or removename == "" then 328 | return 329 | end 330 | if not confirm("你确定要删除 "..removename.." 吗?") then 331 | return 332 | end 333 | filepath = aeg.path.global_lib..removename..".lua" 334 | --DD("删除路径:",filepath) 335 | --DD("文件存在性检测:",file_exists(filepath)) 336 | res,err = os.remove(filepath) 337 | if res == nil then 338 | println("删除失败,可能权限不足,请尝试管理员权限打开Aegisub") 339 | println("错误原文:",err) 340 | return 341 | end 342 | alert("删除成功") 343 | end 344 | function removescript() 345 | -- 遍历脚本目录 346 | local scriptpath = aeg.path.global_script 347 | local slist = {} 348 | for path in lfs.dir(scriptpath) do 349 | if string.sub(path,-4) == ".lua" then 350 | local unit = string.sub(path,0,-5) 351 | table.insert(slist,unit) 352 | end 353 | end 354 | config = { 355 | {class="label",label ="自动化脚本列表",x=0,y=0,width = 5,height = 1} 356 | ,{class = "dropdown",value = "",items = slist,name = "dr",hint="选择需要卸载的插件",x=0,y=1,width = 5,height = 1} 357 | } 358 | -- 显示对话框 359 | btn, btnresult = aegisub.dialog.display(config) 360 | removename = btnresult.dr 361 | if btn == false or removename == "" then 362 | return 363 | end 364 | if not confirm("你确定要删除 "..removename.." 吗?") then 365 | return 366 | end 367 | filepath = aeg.path.global_script..removename..".lua" 368 | --DD("删除路径:",filepath) 369 | --DD("文件存在性检测:",file_exists(filepath)) 370 | res,err = os.remove(filepath) 371 | if res == nil then 372 | println("删除失败,可能权限不足,请尝试管理员权限打开Aegisub") 373 | println("错误原文:",err) 374 | return 375 | end 376 | alert("删除成功") 377 | end 378 | 379 | 380 | -- 菜单结构 381 | varmenu = { 382 | menus.next("当前已安装环境" 383 | ,envTestMenu 384 | ) 385 | ,menus.next("打开AEG目录" 386 | ,{ 387 | menus.menu("根目录",nil,function() os_openpath(aeg.path.data) end,nil,nil) 388 | ,menus.menu("脚本目录",nil,function() os_openpath(aeg.path.global_script) end,nil,nil) 389 | ,menus.menu("库目录",nil,function() os_openpath(aeg.path.global_lib) end,nil,nil) 390 | ,menus.menu("滤镜目录(DLL)",nil,function() os_openpath(aeg.path.data.."csri\\") end,nil,nil) 391 | ,menus.menu("自动保存目录",nil,function() os_openpath(aeg.path.autosave) end,nil,nil) 392 | ,menus.menu("自动备份目录",nil,function() os_openpath(aeg.path.autoback) end,nil,nil) 393 | ,menus.menu("辅助函数库配置文件目录",nil,function() os_openpath(aeg.path.config) end,nil,nil) 394 | } 395 | ) 396 | ,menus.menu("加载库",nil,loadlib,nil,nil) 397 | ,menus.menu("卸载库",nil,removelib,nil,nil) 398 | ,menus.menu("安装自动化脚本(全局)",nil,loadscript,nil,nil) 399 | ,menus.menu("卸载自动化脚本(全局)",nil,removescript,nil,nil) 400 | ,menus.about() 401 | } 402 | 403 | -- 应用菜单设置 404 | tools.automenu("自动装载",varmenu) 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/[使用说明]AEG自动化辅助装载器.txt: -------------------------------------------------------------------------------- 1 | 1、打开Aeg → 菜单 → 自动化 → 自动化(脚本管理器) → 载入 → 选择插件本体(AEG自动化辅助装载器 - 自动化 脚本管理 加载.lua) 2 | 3 | 2、加载完成后 在自动化菜单里可以看到 自动装载 - 未初始化 的选项(如果没有未初始化字样表示可以直接使用) 4 | 5 | 3、选择 自动装载 - 未初始化 选择库文件(CX_AEG插件辅助函数库.lua) 即可将所需的库安装(需要管理员权限启动Aegisub) 6 | 7 | 4、安装完成后在 自动化脚本管理器里选择这个插件 点击重新载入即可使用,或者重启Aegisub 重复步骤1。 8 | 9 | 5、安装完成,库的安装只需要一次,通过载入按钮载入的是本地脚本,可以通过插件内置的选项将本插件安装至全局插件 10 | 11 | 6、如有问题联系 晨轩°(3309003591) 反馈。 -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/[复查小助手]使用说明v2.0.1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/当前版本/[复查小助手]使用说明v2.0.1.docx -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/[复查小助手]使用说明v2.0.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/当前版本/[复查小助手]使用说明v2.0.1.pdf -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/[复查小助手]安装说明v2.0.1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/当前版本/[复查小助手]安装说明v2.0.1.docx -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/[复查小助手]安装说明v2.0.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan353/Lua_Aegisub32/8ed443357b364ea622ba872021c108156831e16c/复查助手-已发布版本存档/当前版本/[复查小助手]安装说明v2.0.1.pdf -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/前置库/CX_AEG插件辅助函数库.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | AEG插件辅助函数库 3 | 版权所属:晨轩°(QQ3309003591) 4 | 接口文档 5 | ]] 6 | -- 模块基础信息、接口及常量定义 7 | -- 版本号 8 | lib_version = "1.0.1dev" 9 | -- 更新日志 10 | lib_ChangeLog = [[ 11 | 1.0.1dev 12 | 支持了多选菜单的双向绑定 13 | ]] 14 | 15 | local lfs = require "lfs" 16 | local re = require 'aegisub.re' 17 | local LEVEL = { 18 | FATAL = 0 19 | ,ERROR = 1 20 | ,WARNING = 2 21 | ,INFO = 3 -- AEG默认等级 22 | ,DEBUG = 4 23 | ,TRACK = 5 24 | } 25 | local print_level = LEVEL.INFO -- 默认输出等级,用于输出文本 26 | local close_configwrite = false -- 关闭配置文件写入(通过本函数库执行的写入都将被禁止) 27 | -- 设置接口 28 | function setLevel(level) 29 | if type(level) ~= "number" and ( level >= 1 and level <= 5 ) then 30 | error("设置的等级必须为整数,且在1~5之间。") 31 | end 32 | print_level = math.modf(level) 33 | end 34 | function getLevel() 35 | return print_level 36 | end 37 | function setCloseConfigWrite(b) 38 | close_configwrite = (b == true) 39 | end 40 | function getCloseConfigWrite() 41 | return close_configwrite 42 | end 43 | 44 | -- 设置表 45 | local setting = { 46 | LEVEL = LEVEL 47 | ,setLevel = setLevel -- 设置默认输出等级 48 | ,getLevel = getLevel 49 | ,setCloseConfigWrite = setCloseConfigWrite -- 设置关闭配置写入 50 | ,getCloseConfigWrite = getCloseConfigWrite 51 | } 52 | -- AEG兼容定义(用于使插件在AEG中拥有与IDE相同的响应) 53 | -- 等级输出 54 | function levelout(level,vaule) 55 | aegisub.debug.out(level, vaule) 56 | end 57 | -- 标准输出 58 | function standout(vaule) 59 | aegisub.debug.out(print_level, vaule) 60 | end 61 | -- 打印所有能打印的类型 62 | function valueToStr(value) 63 | if value == nil then 64 | return "nil" 65 | end 66 | if type(value) ~= "table" then 67 | return tostring(value) 68 | end 69 | res = "" 70 | -- print覆盖 71 | local function print(val) 72 | if val == nil then 73 | res = res .. 'nil'..'\n' 74 | else 75 | res = res .. tostring(val)..'\n' 76 | end 77 | end 78 | -- 好用的table输出函数 79 | local function print_r(t) 80 | local print_r_cache={} 81 | local function sub_print_r(t,indent) 82 | if (print_r_cache[tostring(t)]) then 83 | print(indent.."*"..tostring(t)) 84 | else 85 | print_r_cache[tostring(t)]=true 86 | if (type(t)=="table") then 87 | for pos,val in pairs(t) do 88 | pos = tostring(pos) 89 | if (type(val)=="table") then 90 | print(indent.."["..pos.."] => "..tostring(t).." {") 91 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 92 | print(indent..string.rep(" ",string.len(pos)+6).."}") 93 | elseif (type(val)=="string") then 94 | print(indent.."["..pos..'] => "'..val..'"') 95 | else 96 | print(indent.."["..pos.."] => "..tostring(val)) 97 | end 98 | end 99 | else 100 | print(indent..tostring(t)) 101 | end 102 | end 103 | end 104 | if (type(t)=="table") then 105 | print(tostring(t).." {") 106 | sub_print_r(t," ") 107 | print("}") 108 | else 109 | sub_print_r(t," ") 110 | end 111 | end 112 | -- 运行函数 113 | print_r(value) 114 | -- 打印结果 115 | return string.sub(res,0,-2) 116 | end 117 | 118 | -- 短输出 119 | function print(...) 120 | local arg = {...} 121 | maxi = 1 122 | for i,v in pairs(arg) do 123 | if i > maxi then maxi = i end 124 | end 125 | printResult = "" 126 | for i = 1,maxi do 127 | printResult = printResult .. valueToStr(arg[i]) 128 | end 129 | standout(printResult) 130 | end 131 | function println(...) 132 | local arg = {...} 133 | maxi = 1 134 | for i,v in pairs(arg) do 135 | if i > maxi then maxi = i end 136 | end 137 | arg[maxi + 1] = "\n" 138 | print(unpack(arg)) 139 | end 140 | -- 完整输出 141 | function var_print(...) 142 | local arg = {...} 143 | maxi = 1 144 | for i,v in pairs(arg) do 145 | if i > maxi then maxi = i end 146 | end 147 | printResult = "" 148 | for i = 1,maxi do 149 | printResult = printResult .. valueToStr(arg[i]) 150 | end 151 | standout(printResult) 152 | end 153 | function var_println(...) 154 | local arg = {...} 155 | maxi = 1 156 | for i,v in pairs(arg) do 157 | if i > maxi then maxi = i end 158 | end 159 | arg[maxi + 1] = "\n" 160 | var_print(unpack(arg)) 161 | end 162 | 163 | --[[ 其它工具集 ]] 164 | -- 创建偏函数 165 | function functool_partial(func,...) 166 | local arg = {...} 167 | local getMerge = function(arg2) 168 | res = {} 169 | for _,v in pairs(arg) do 170 | table.insert(res,v) 171 | end 172 | for _,v in pairs(arg2) do 173 | table.insert(res,v) 174 | end 175 | return res 176 | end 177 | return function(...) 178 | local arg2 = {...} 179 | return func(unpack(getMerge(arg2))) 180 | end 181 | end 182 | 183 | 184 | --[[ 185 | 扩展环境 186 | ]] 187 | -- 合并表 188 | function table.merge(source,addtable) 189 | for k,v in pairs(addtable) do 190 | source[k] = v 191 | end 192 | return source 193 | end 194 | -- 去除首尾空格 195 | local str_trim_expr_pre = re.compile([[^\s+]],re.NOSUB,re.NO_MOD_M) 196 | local str_trim_expr_end = re.compile([[\s+$]],re.NOSUB,re.NO_MOD_M) 197 | function string.trim(str) 198 | out_str, rep_count = str_trim_expr_pre:sub(str,'') 199 | out_str, rep_count1 = str_trim_expr_end:sub(out_str,'') 200 | if not rep_count then rep_count = 0 end 201 | if not rep_count1 then rep_count1 = 0 end 202 | rep_count = rep_count + rep_count1 203 | return out_str 204 | end 205 | 206 | 207 | --[[ 208 | 缩写集 209 | ]] 210 | 211 | 212 | -- 缩写aegisub表 213 | local aeg = aegisub 214 | local aegmin = {} 215 | local aegPath = {} 216 | aeg.regMacro = aegisub.register_macro 217 | aeg.regFilter = aegisub.register_filter 218 | aeg.levelout = levelout 219 | aeg.standout = standout 220 | aeg.exit = aegisub.cancel 221 | aeg.path = aegPath 222 | 223 | setmetatable(aeg, { 224 | __index = function(t, key) 225 | if aegisub.progress ~= nil then 226 | aegmin.setTitle = aegisub.progress.title 227 | aegmin.setProgress = aegisub.progress.set 228 | aegmin.setTask = aegisub.progress.task 229 | aegmin.cancelled = aegisub.progress.is_cancelled 230 | -- 设置还原点 231 | aegmin.setUndo = aegisub.set_undo_point 232 | -- 等待AEG响应,同时判断是否取消执行,用户取消执行将结束脚本,参数为结束运行钩子函数 233 | aegmin.waitAeg = function (func) 234 | if aegisub.progress.is_cancelled() then 235 | if type(func) == "function" then 236 | func() 237 | end 238 | end 239 | end 240 | end 241 | return aegmin[key] 242 | end 243 | }) 244 | -- 获取路径说明,无法获取时返回nil 245 | setmetatable(aegPath, { 246 | __index = function(t, key) 247 | if key == "data" then 248 | -- 存储应用数据的位置。在Windows下是指Aegisub安装目录(.exe的位置)。在Mac OS X下被包含在应用包里。在其他类POSIX系统下的目录为 $prefix/share/aegisub/. 249 | return aegisub.decode_path('?data\\') 250 | 251 | elseif key == "user" then 252 | -- 存储用户数据的位置,例如配置文件,自动备份文件和其他附加的东西。在Windows下,这个路径是 %APPDATA%\Aegisub\; 在Mac OS X下,这个路径是 $HOME/Library/ApplicationSupport/Aegisub/; 在其他类OSIX系统下,这个路径是 $HOME/.aegisub/; 在便携版Aegisub中这个目录是 ?data。 253 | return aegisub.decode_path('?user\\') 254 | 255 | elseif key == "temp" then 256 | -- 系统临时文件目录。音频缓存和临时字幕文件都存储在这个位置。 257 | return aegisub.decode_path('?temp') 258 | 259 | elseif key == "local" or key == "cache" then 260 | -- 本地用户设置路径。存储运行缓存文件的位置,例如FFMS2索引和字体配置缓存。Windows下为 %LOCALAPPDATA%\Aegisub 其他系统是 ?user。 261 | return aegisub.decode_path('?local\\') 262 | 263 | elseif key == "script" then 264 | -- 只有当你打开一个已经保存在本地的字幕文件时才有作用,为该字幕文件的保存的位置。 265 | text = aegisub.decode_path('?script\\') 266 | if text == '?script\\' then text = nil end 267 | return text 268 | 269 | elseif key == "video" then 270 | -- 只有读取本地视频后才有作用,为当前读取视频文件的路径,注意读取空白视频时是无法使用该路径的。 271 | text = aegisub.decode_path('?video\\') 272 | if text == '?video\\' then text = nil end 273 | return text 274 | 275 | elseif key == "audio" then 276 | -- 只有读取本地音频后才有作用,为当前读取音频文件的路径,注意读取空白音频时是无法使用该路径的。 277 | text = aegisub.decode_path('?audio\\') 278 | if text == '?audio\\' then text = nil end 279 | return text 280 | -- 扩展路径 281 | elseif key == "config" then 282 | -- 配置文件路径 283 | text = aegisub.decode_path('?user\\').."script_config\\" 284 | lfs.mkdir(text) 285 | return text 286 | 287 | elseif key == "global_script" then 288 | -- 自动化脚本文件路径 289 | text = aegisub.decode_path('?data\\').."automation\\autoload\\" 290 | return text 291 | 292 | elseif key == "global_lib" then 293 | -- 库文件路径 294 | text = aegisub.decode_path('?data\\').."automation\\include\\" 295 | return text 296 | 297 | elseif key == "autosave" then 298 | -- 自动保存路径 299 | text = aegisub.decode_path('?user\\').."autosave\\" 300 | return text 301 | 302 | elseif key == "autoback" then 303 | -- 自动备份路径 304 | text = aegisub.decode_path('?user\\').."autoback\\" 305 | return text 306 | 307 | end 308 | return nil 309 | end 310 | }) 311 | 312 | -- 函数定义 313 | --[[ 314 | 显示集 315 | ]] 316 | 317 | -- 显示一个简单的窗口,参数(提示信息[,类型标识[,默认值]]) 318 | --[[ 319 | 类型标识(返回类型): 320 | 0-提示框(nil),提示需要每行尽可能短(不超过9个字) 321 | 1-确认取消框(bool) 322 | 2-单行文本输入框(string or nil) 323 | 3-单行整数输入框(number or nil) 324 | 4-单行小数输入框(number or nil) 325 | 注意:整数与小数输入有误时不会限制或报错,可能得到奇怪的结果。 326 | ]] 327 | function QuickWindow(msg,type_num,default) 328 | local config = {} -- 窗口配置 329 | local result = nil -- 返回结果 330 | local buttons = nil 331 | local button_ids = nil 332 | if type(msg) ~= 'string' then 333 | error('display.confirm参数错误-1,提示信息必须存在且为文本类型!',2) 334 | end 335 | if type_num == nil then type_num = 0 end 336 | if type(type_num) ~= 'number' then 337 | -- db.var_export(type_num) 338 | error('display.confirm参数错误-2,类型标识必须是数值!',2) 339 | end 340 | 341 | if type_num == 0 then 342 | config = { 343 | {class="label", label=msg, x=0, y=0,width=8} 344 | } 345 | buttons = {aegisub.gettext'Yes'} 346 | button_ids = {ok = aegisub.gettext'Yes'} 347 | elseif type_num == 1 then 348 | config = { 349 | {class="label", label=msg, x=0, y=0,width=12} 350 | } 351 | elseif type_num == 2 then 352 | if default == nil then default = '' end 353 | config = { 354 | {class="label", label=msg, x=0, y=0,width=12}, 355 | {class="edit", name='text',text=tostring(default), x=0, y=1,width=12} 356 | } 357 | elseif type_num == 3 then 358 | if default == nil then default = 0 end 359 | if type(type_num) ~= 'number' then 360 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 361 | end 362 | config = { 363 | {class="label", label=msg, x=0, y=0,width=12}, 364 | {class="intedit", name='int',value=default, x=0, y=1,width=12} 365 | } 366 | elseif type_num == 4 then 367 | if default == nil then default = 0 end 368 | if type(type_num) ~= 'number' then 369 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 370 | end 371 | config = { 372 | {class="label", label=msg, x=0, y=0,width=12}, 373 | {class="floatedit", name='float',value=default, x=0, y=1,width=12} 374 | } 375 | elseif type_num == 5 then 376 | if default == nil then default = '' end 377 | if type(type_num) ~= 'number' then 378 | error('display.confirm参数错误-3,此标识的默认值必须为数值!',2) 379 | end 380 | config = { 381 | {class="label", label=msg, x=0, y=0,width=12}, 382 | {class="textbox", name='textbox',text=tostring(default), x=0, y=1,width=15,height=10} 383 | } 384 | else 385 | error('display.confirm参数错误,无效的类型标识!',2) 386 | end 387 | 388 | -- 显示对话框 389 | btn, btnresult = aegisub.dialog.display(config,buttons,button_ids) 390 | 391 | -- 处理并返回结果 392 | if type_num == 0 then 393 | result = nil 394 | elseif type_num == 1 then 395 | if btn ~= false then 396 | result = true 397 | else 398 | result = false 399 | end 400 | elseif type_num == 2 then 401 | if btn ~= false then 402 | result = btnresult.text 403 | end 404 | elseif type_num == 3 then 405 | if btn ~= false then 406 | result = btnresult.int 407 | end 408 | elseif type_num == 4 then 409 | if btn ~= false then 410 | result = btnresult.float 411 | end 412 | elseif type_num == 5 then 413 | if btn ~= false then 414 | result = btnresult.textbox 415 | end 416 | end 417 | -- debug.var_export(result) 418 | return result 419 | end 420 | 421 | -- JS拟态提示框 422 | -- 警告框 参数 显示的消息 | 返回值 nil 423 | function alert(msg) 424 | return QuickWindow(msg,0,"") 425 | end 426 | -- 确认框 参数 显示的消息 | 返回值 true/false 427 | function confirm(msg) 428 | return QuickWindow(msg,1,"") 429 | end 430 | -- 文本输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 431 | function prompt(msg,defval) 432 | return QuickWindow(msg,2,defval) 433 | end 434 | input = prompt 435 | 436 | -- 文本域输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 437 | function inputTextArea(msg,defval) 438 | return QuickWindow(msg,5,defval) 439 | end 440 | 441 | -- 整数输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 442 | function inputInt(msg,defval) 443 | return QuickWindow(msg,3,defval) 444 | end 445 | -- 小数输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 446 | function inputFloat(msg,defval) 447 | return QuickWindow(msg,4,defval) 448 | end 449 | 450 | 451 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 452 | select_file_last_select_path = "" 453 | function select_file(title, wildcards, default_dir, must_exist, default_file, allow_multiple) 454 | if title == nil then title = '选择文件' end 455 | if default_file == nil then default_file = '' end 456 | if default_dir == nil then 457 | default_dir = '' 458 | if select_file_last_select_path then 459 | default_dir = select_file_last_select_path 460 | else 461 | temp_path = aegisub.decode_path('?script\\file') 462 | if temp_path ~= '?script\\file' then 463 | default_dir = temp_path 464 | end 465 | end 466 | end 467 | if wildcards == nil then wildcards = '所有文件(*)|*' end 468 | if allow_multiple == nil then allow_multiple = false end 469 | if must_exist == nil then must_exist = true end 470 | file_name = aegisub.dialog.open(title, default_file, default_dir, wildcards, allow_multiple, must_exist) 471 | if file_name then select_file_last_select_path = file_name end 472 | return file_name 473 | end 474 | 475 | local display = { 476 | -- 显示一个简单的提示窗口,参数(提示信息[,类型标识[,默认值]]) 477 | QuickWindow = QuickWindow 478 | ,alert = alert -- 警告框 参数 显示的消息 | 返回值 nil 479 | ,confirm = confirm -- 确认框 参数 显示的消息 | 返回值 true/false 480 | ,inputTextArea = inputTextArea 481 | ,prompt = prompt -- 文本输入框 参数 显示的消息,默认值 | 返回值 用户文本/nil 482 | ,input = input 483 | ,inputInt = inputInt 484 | ,inputFloat = inputFloat 485 | -- 打开文件选择窗口(标题,过滤器,默认目录,所选文件必须存在,默认文件名,允许多选) 返回 文件路径 486 | ,select_file = select_file 487 | } 488 | 489 | --[[ 490 | 字幕行工具 491 | ]] 492 | function subs_getFirstDialogue(subs) 493 | for i = 1,#subs do 494 | if subs[i].class == 'dialogue' then 495 | return i 496 | end 497 | end 498 | end 499 | function subs_getStyles(subs) 500 | styles = {cout_n=0} -- 字幕行 501 | infos = {cout_n=0} -- 信息行 502 | unknows = {cout_n=0} -- 未知行 503 | first_dialogue = nil 504 | for i = 1,#subs do 505 | line = subs[i] 506 | if line.class == 'style' then 507 | styles.cout_n = styles.cout_n + 1 508 | styles[line.name] = { 509 | index = i 510 | ,line = line 511 | } 512 | elseif line.class == 'info' then 513 | infos.cout_n = infos.cout_n + 1 514 | infos[line.key:lower()] = { 515 | index = i 516 | ,line = line 517 | ,key = line.key 518 | ,value = line.value 519 | } 520 | elseif line.class == 'unknown' then 521 | unknows.cout_n = unknows.cout_n + 1 522 | table.insert(unknown,{ 523 | index = i 524 | ,line = line 525 | }) 526 | elseif line.class == 'dialogue' then 527 | first_dialogue = i 528 | break 529 | end 530 | end 531 | return styles,first_dialogue,infos,unknows 532 | end 533 | function subs_collect_head(subs) 534 | styles,first_dialogue,infos,unknows = subs_getStyles(subs) 535 | return { 536 | firstDialogue = first_dialogue 537 | ,styles = styles 538 | ,infos = infos 539 | ,unknows = unknows 540 | } 541 | end 542 | function subs_pre_deal(subs) 543 | --[[ 544 | 代理原subs对象 545 | 解决各种不爽的问题,尽可能降低因操作subs导致的AEG崩溃可能 546 | ]] 547 | heads = subs_collect_head(subs) 548 | local function getIter(tab,start_i,end_i) 549 | local si = start_i 550 | local ni = si 551 | local ei = end_i 552 | return function () 553 | ni = ni + 1 554 | if (ni - 1) <= ei then 555 | return ni - si,tab[ni - 1] 556 | end 557 | end 558 | end 559 | local function getCollectIter(tab) 560 | local tabiter = pairs(tab) 561 | local k = nil 562 | return function () 563 | k,v = tabiter(tab,k) 564 | if k == "cout_n" then 565 | k,v = tabiter(tab,k) 566 | end 567 | if k ~= nil then 568 | return v.index,v.line 569 | end 570 | end 571 | end 572 | proxySubs = { 573 | subs = subs 574 | ,heads = heads 575 | ,otherData = { 576 | -- 暂未确定 577 | } 578 | -- 获取原始行标 579 | ,getSourceIndex = function (i) 580 | return i + proxySubs.heads.firstDialogue - 1 581 | end 582 | -- 获取重定向行标 583 | ,getIndex = function (i) 584 | return i - proxySubs.heads.firstDialogue - 1 585 | end 586 | -- 兼容定义 587 | ,raw_insert = subs.insert -- 插入 588 | ,raw_delete = subs.delete -- 删除 589 | ,raw_deleterange = subs.deleterange -- 范围删除 590 | ,raw_append = subs.append -- 追加 591 | -- 重定向定义 592 | ,insert = function (i,...) 593 | if i < 0 then 594 | error("Out of bounds",2) 595 | end 596 | i = i + proxySubs.heads.firstDialogue - 1 597 | if i > #(proxySubs.subs) then 598 | error("Out of bounds",2) 599 | end 600 | return subs.insert(i,...) 601 | end 602 | ,delete = function (...) 603 | local args = {...} 604 | for i = 1, #args do 605 | if args[i] < 0 then 606 | error("Out of bounds",2) 607 | end 608 | args[i] = args[i] + proxySubs.heads.firstDialogue - 1 609 | if args[i] > #(proxySubs.subs) then 610 | error("Out of bounds",2) 611 | end 612 | end 613 | return subs.delete(unpack(args)) 614 | end 615 | ,deleterange = function (first,last) 616 | if first < 0 or last < 0 then 617 | error("Error delete range",2) 618 | end 619 | first = first + proxySubs.heads.firstDialogue - 1 620 | last = last + proxySubs.heads.firstDialogue - 1 621 | if first > #(proxySubs.subs) or last > #(proxySubs.subs) then 622 | error("Out of bounds",2) 623 | end 624 | 625 | if first > last then 626 | return 627 | end 628 | return subs.deleterange( 629 | first 630 | ,last 631 | ) 632 | end 633 | 634 | ,append = function (...) 635 | local args = {...} 636 | for _,v in pairs(args) do 637 | if type(v) ~= "table" or v.class ~= "dialogue" then 638 | error("插入了不合法的对话行",2) 639 | end 640 | end 641 | return subs.append(unpack(args)) 642 | end 643 | 644 | } 645 | setmetatable(proxySubs,{ 646 | __index = function (mytable, key) 647 | -- 元素数量数据 648 | if key == "n" then 649 | return #rawget(mytable,"subs") 650 | elseif key == "sn" then -- 字幕数 651 | return rawget(mytable,"heads").styles.cout_n 652 | elseif key == "in" or key == "i_n" then -- 信息行数量 653 | return rawget(mytable,"heads").infos.cout_n 654 | elseif key == "dn" then -- 对话行数量 655 | return #rawget(mytable,"subs") - rawget(mytable,"heads").firstDialogue + 1 656 | elseif key == "fd" then -- 对话行数量 657 | return rawget(mytable,"heads").firstDialogue 658 | 659 | -- 收集的表数据 660 | elseif key == "styles" then -- 样式行表 661 | return rawget(mytable,"heads").styles 662 | elseif key == "infos" then -- 信息行表 663 | return rawget(mytable,"heads").infos 664 | -- 迭代器 665 | elseif key == "InfosIter" or key == "infosIter" or key == "II" then -- 信息行表迭代器 666 | return getCollectIter(rawget(mytable,"heads").infos) 667 | elseif key == "StylesIter" or key == "stylesIter" or key == "SI" then -- 样式行表迭代器 668 | return getCollectIter(rawget(mytable,"heads").styles) 669 | elseif key == "DialogueIter" or key == "dialogueIter" or key == "DI" then -- 对话行表迭代器 670 | local tsubs = rawget(mytable,"subs") 671 | return getIter(tsubs,rawget(mytable,"heads").firstDialogue,#tsubs) 672 | end 673 | 674 | -- 对话行设置重定向 675 | if type(key) == "number" then 676 | if key <= 0 then 677 | error("Out of bounds",2) 678 | end 679 | key = key + rawget(mytable,"heads").firstDialogue - 1 680 | return rawget(mytable,"subs")[key] 681 | end 682 | return rawget(mytable,"subs")[key] 683 | end 684 | ,__newindex = function (mytable, key, value) 685 | -- 设置新的值都导向subs 686 | -- 对话行设置重定向 687 | if type(key) == "number" then 688 | if key <= 0 then 689 | error("Out of bounds",2) 690 | end 691 | if type(value) ~= "table" or value.class ~= "dialogue" then 692 | error("使用了不合法的对话行",2) 693 | end 694 | key = key + rawget(mytable,"heads").firstDialogue - 1 695 | end 696 | rawget(mytable,"subs")[key] = value 697 | if type(key) == "number" and key < heads.firstDialogue then 698 | -- 如果设置的值小于第一行对话行位置则重新收集行信息 699 | rawset(mytable,"heads",subs_collect_head(rawget(mytable,"subs"))) 700 | end 701 | end 702 | }) 703 | return proxySubs 704 | end 705 | --[[ 706 | 字幕辅助表 707 | ]] 708 | local subsTool = { 709 | -- 使用subs字幕对象 收集样式信息表 并返回样式表与首个dialogue行的下标 710 | getStyles = subs_getStyles 711 | -- 获取首个dialogue行的下标 712 | ,getFirstDialogue = subs_getFirstDialogue 713 | -- 字幕行预处理 714 | ,presubs = subs_pre_deal 715 | } 716 | 717 | 718 | 719 | --[[ 720 | 其它工具集 721 | ]] 722 | -- 序列化 723 | function tool_serialize(obj) 724 | local lua = "" 725 | local t = type(obj) 726 | if t == "number" then 727 | lua = lua .. obj 728 | elseif t == "boolean" then 729 | lua = lua .. tostring(obj) 730 | elseif t == "string" then 731 | lua = lua .. string.format("%q", obj) 732 | elseif t == "table" then 733 | lua = lua .. "{" 734 | for k, v in pairs(obj) do 735 | lua = lua .. "[" .. tool_serialize(k) .. "]=" .. tool_serialize(v) .. "," 736 | end 737 | local metatable = getmetatable(obj) 738 | if metatable ~= nil and type(metatable.__index) == "table" then 739 | for k, v in pairs(metatable.__index) do 740 | lua = lua .. "[" .. tool_serialize(k) .. "]=" .. tool_serialize(v) .. "," 741 | end 742 | end 743 | lua = lua .. "}" 744 | elseif t == "nil" then 745 | return nil 746 | else 747 | error("can not serialize a " .. t .. " type.") 748 | end 749 | return lua 750 | end 751 | -- 反序列化 752 | function tool_unserialize(lua) 753 | local t = type(lua) 754 | if t == "nil" or lua == "" then 755 | return nil 756 | elseif t == "number" or t == "string" or t == "boolean" then 757 | lua = tostring(lua) 758 | else 759 | error("can not unserialize a " .. t .. " type.") 760 | end 761 | lua = "return " .. lua 762 | local func = loadstring(lua) 763 | if func == nil then 764 | return nil 765 | end 766 | return func() 767 | end 768 | 769 | -- 合并路径与文件名 770 | function getFilePath(filename,path) 771 | if filename == nil or filename == "" then 772 | error("文件名不能为空或nil") 773 | end 774 | if path == nil then 775 | error("文件路径不能为nil") 776 | end 777 | local filepath = "" 778 | if string.sub(path, -2) == "\\" then 779 | filepath = "\\" 780 | end 781 | filepath = tostring(path) .. filepath .. tostring(filename) 782 | return filepath 783 | end 784 | -- 读取一个简单配置文件(xxx.ini) 785 | function tool_readini(filename,path) 786 | if path == nil or path == "" then path = aeg.path.config end 787 | local filepath = getFilePath(filename,path) 788 | res = {} 789 | file = io.open(filepath, "r") 790 | if not io.type(file) then 791 | error("[读取]简单配置文件 "..filepath.." 无法打开!") 792 | end 793 | line = file:read() 794 | if line ~= "[default]" then 795 | file:close() 796 | error("[读取]简单配置文件 "..filepath.." 内容不合法!") 797 | end 798 | line = file:read() 799 | while line do 800 | pos = string.find(line,"=") 801 | if pos == nil then 802 | file:close() 803 | error("[读取]简单配置文件 "..filepath.." 内容不合法!") 804 | end 805 | key = string.sub(line,0,pos - 1) 806 | val = string.sub(line,pos + 1,-1) 807 | val = string.gsub(val,"\\n","\n") 808 | val = string.gsub(val,"\\\\","\\") 809 | res[key] = val 810 | 811 | line = file:read() 812 | end 813 | -- 关闭打开的文件 814 | file:close() 815 | return res 816 | end 817 | -- 写入一个简单配置文件(只支持字符串或数值为键,布尔、字符串或数值为值。) 818 | function tool_writeini(tabval,filename,path) 819 | if path == nil or path == "" then path = aeg.path.config end 820 | local filepath = getFilePath(filename,path) 821 | if type(tabval) ~= "table" then 822 | error("[写入]写入配置文件时错误!写入类型异常!错误文件->"..filepath) 823 | end 824 | file = io.open(filepath, "w+") 825 | if not io.type(file) then 826 | file:close() 827 | error("[写入]简单配置文件 "..filepath.." 无法打开!") 828 | end 829 | file:write("[default]\n") 830 | for k,v in pairs(tabval) do 831 | v = tostring(v) 832 | v = string.gsub(v,"\\","\\\\") 833 | v = string.gsub(v,"\n","\\n") 834 | v = v.."\n" 835 | file:write(k.."="..v) 836 | end 837 | -- 关闭打开的文件 838 | file:close() 839 | end 840 | 841 | -- 读配置 参数为签名 842 | function tool_readConfig(signature) 843 | if signature == nil then 844 | if _G.script_signature == nil then 845 | error("读写配置的签名不能为空!") 846 | else 847 | signature = _G.script_signature 848 | end 849 | end 850 | local res = {} 851 | local status = xpcall( 852 | function () 853 | res = tool_readini(signature..".ini") 854 | end 855 | , 856 | function (err) 857 | levelout(4,err) 858 | levelout(4,"\n") 859 | end 860 | ) 861 | return res 862 | end 863 | -- 写配置 参数为要写入的内容,签名 864 | function tool_saveConfig(conftab,signature) 865 | if signature == nil then 866 | if _G.script_signature == nil then 867 | error("读写配置的签名不能为空!") 868 | else 869 | signature = _G.script_signature 870 | end 871 | end 872 | if conftab == nil then 873 | error("保存的配置不能为空!") 874 | end 875 | if close_configwrite then 876 | levelout(4,"配置文件写入已关闭") 877 | return 878 | end 879 | local res = {} 880 | local status = xpcall( 881 | function () 882 | res = tool_writeini(conftab,signature..".ini") 883 | end 884 | , 885 | function (err) 886 | levelout(4,err) 887 | levelout(4,"\n") 888 | end 889 | ) 890 | end 891 | -- 读指定配置 参数为 键名,签名 892 | function tool_getConfig(key,signature) 893 | return tool_readConfig(signature)[key] 894 | end 895 | -- 写指定配置 参数为 键名,值,签名 896 | function tool_setConfig(key,val,signature) 897 | res = tool_readConfig(signature) 898 | res[key] = val 899 | tool_saveConfig(res,signature) 900 | end 901 | -- 清空所有配置 参数为 签名 902 | function tool_clearConfig(signature) 903 | tool_saveConfig({},signature) 904 | end 905 | -- 创建绑定配置的菜单(需要定义脚本签名才可使用) 参数:菜单前缀,菜单名,绑定的表-键只能为文本-值只能为bool 906 | function tool_MultSelectMenu(prefix,name,bindtab,des) 907 | local res = tool_readConfig() 908 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 909 | prefix = prefix.."/" 910 | end 911 | if string.sub(name, -2) == "/" then 912 | name = string.sub(name, 0, -2) 913 | end 914 | if des == nil then 915 | des = "" 916 | end 917 | local getFk = function (name,k) 918 | return "verif_"..name.."_"..k 919 | end 920 | local menu = { 921 | rf = function(prefix,name,bindtab,key) 922 | bindtab[key] = not bindtab[key] 923 | tool_setConfig(getFk(name,key),bindtab[key]) 924 | end 925 | ,af = function(prefix,name,bindtab,key) 926 | return bindtab[key] 927 | end 928 | } 929 | 930 | local bind_metatable = { 931 | __index = function(mytable, key) 932 | if key == "set" then 933 | return functool_partial(menu.rf,prefix,name,mytable) 934 | elseif key == "get" then 935 | return functool_partial(menu.af,prefix,name,mytable) 936 | end 937 | end 938 | } 939 | setmetatable(bindtab,bind_metatable) 940 | for k,v in pairs(bindtab) do 941 | if type(k) ~= "string" then 942 | error("错误的绑定表",2) 943 | end 944 | if type(v) ~= "boolean" then 945 | error("错误的绑定表值,绑定的值只能为boolean类型",2) 946 | end 947 | if res[getFk(name,k)] == nil then 948 | res[getFk(name,k)] = v 949 | else 950 | bindtab[k] = (res[getFk(name,k)] == "true") 951 | end 952 | aeg.regMacro( 953 | prefix..name.."/"..k 954 | ,name.."-"..des 955 | ,functool_partial(menu.rf,prefix,name,bindtab,k) 956 | ,nil 957 | ,functool_partial(menu.af,prefix,name,bindtab,k) 958 | ) 959 | end 960 | 961 | end 962 | 963 | -- 创建绑定配置的单选菜单(多选一) 参数:菜单前缀,菜单名,绑定的数组-只能为数组-键now标识当前选择 964 | function tool_SingleSelectMenu(prefix,name,bindtab,des) 965 | local res = tool_readConfig() 966 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 967 | prefix = prefix.."/" 968 | end 969 | if string.sub(name, -2) == "/" then 970 | name = string.sub(name, 0, -2) 971 | end 972 | if des == nil then 973 | des = "" 974 | end 975 | local getFk = function (name,k) 976 | return "verif_"..name.."_"..k 977 | end 978 | local menu = { 979 | rf = function(prefix,name,bindtab,key) 980 | bindtab["now"] = key 981 | tool_setConfig(getFk(name,"now"),key) 982 | end 983 | ,af = function(prefix,name,bindtab,key) 984 | return bindtab["now"] == key 985 | end 986 | } 987 | local bind_metatable = { 988 | __index = function(mytable, key) 989 | if key == "set" then 990 | return functool_partial(menu.rf,prefix,name,mytable) 991 | elseif key == "get" then 992 | return functool_partial(menu.af,prefix,name,mytable) 993 | end 994 | end 995 | } 996 | setmetatable(bindtab,bind_metatable) 997 | for k,v in pairs(bindtab) do 998 | if type(k) ~= "string" then 999 | error("错误的绑定表,单选菜单键只能为字符串或now",2) 1000 | end 1001 | if k == "now" and type(v) ~= "string" then 1002 | error("错误的绑定表,now键对应的值只能为字符串",2) 1003 | end 1004 | if k == "now" then 1005 | if res[getFk(name,k)] then 1006 | bindtab["now"] = res[getFk(name,k)] 1007 | end 1008 | goto forend 1009 | end 1010 | aeg.regMacro( 1011 | prefix..name.."/"..k 1012 | ,name.."-"..des 1013 | ,functool_partial(menu.rf,prefix,name,bindtab,k) 1014 | ,nil 1015 | ,functool_partial(menu.af,prefix,name,bindtab,k) 1016 | ) 1017 | ::forend:: 1018 | end 1019 | 1020 | end 1021 | 1022 | -- 创建菜单类 1023 | function tool_next(name,nexttable,defaultmenu) 1024 | -- 创建下级菜单 1025 | return { 1026 | class = "next" 1027 | ,name = name 1028 | ,nexttable = nexttable 1029 | ,default = defaultmenu 1030 | } 1031 | end 1032 | 1033 | function tool_About() 1034 | -- 关于的显示函数 1035 | local function about() 1036 | msg = '' 1037 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n" 1038 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n" 1039 | msg = msg..tostring(_G.script_name)..',感谢您的使用!'.."\n".."\n" 1040 | msg = msg..'----更新日志结束----'.."\n" 1041 | msg = msg..tostring(_G.script_ChangeLog) .. "\n" 1042 | msg = msg..'-↑↑-更新日志-↑↑-'.."\n" 1043 | msg = msg..'----'..tostring(_G.script_name)..'----'.."\n" 1044 | msg = msg..'作者:'..tostring(_G.script_author).."\n" 1045 | msg = msg..'版本:'..tostring(_G.script_version).."\n" 1046 | msg = msg..'描述:'..tostring(_G.script_description).."\n" 1047 | msg = msg..'\n>>>>关于<<<<'.."\n"..tostring(_G.script_about).."\n" 1048 | aegisub.debug.out(0, msg.."\n") 1049 | end 1050 | return { 1051 | class = "menu" 1052 | ,name = "关于" 1053 | ,des = "显示关于" 1054 | ,processing = about 1055 | ,validation = nil 1056 | ,is_active = nil 1057 | } 1058 | end 1059 | function tool_Menu(name,des,processing,validation,is_active) 1060 | if name == nil or type(name) ~= "string" then 1061 | error("菜单的名称必须是个有效的文本",2) 1062 | end 1063 | if processing == nil then 1064 | error("菜单的应用函数必须存在",2) 1065 | end 1066 | return { 1067 | class = "menu" 1068 | ,name = name 1069 | ,des = des 1070 | ,processing = processing 1071 | ,validation = validation 1072 | ,is_active = is_active 1073 | } 1074 | end 1075 | function tool_CheckBoxMenu(name,bind,des) 1076 | if type(bind) ~= "table" then 1077 | error("绑定的对象必须是表格",2) 1078 | end 1079 | return { 1080 | class = "checkbox" 1081 | ,name = name 1082 | ,des = des 1083 | ,bind = bind 1084 | } 1085 | end 1086 | function tool_RadioMenu(name,bind,des) 1087 | if type(bind) ~= "table" or bind.now == nil then 1088 | error("绑定的对象必须是表格,且必须拥有now键",2) 1089 | end 1090 | return { 1091 | class = "radio" 1092 | ,name = name 1093 | ,des = des 1094 | ,bind = bind 1095 | } 1096 | end 1097 | 1098 | -- 应用菜单类 1099 | function tool_applyMenu(prefix,name,menu) 1100 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 1101 | prefix = prefix.."/" 1102 | end 1103 | if string.sub(name, -2) == "/" then 1104 | name = string.sub(name, 0, -2) 1105 | end 1106 | if menu.des == nil then 1107 | menu.des = name 1108 | end 1109 | if menu.class == "menu" then 1110 | aeg.regMacro( 1111 | prefix..name.."/"..menu.name 1112 | ,menu.des 1113 | ,menu.processing 1114 | ,menu.validation 1115 | ,menu.is_active 1116 | ) 1117 | elseif menu.class == "checkbox" then 1118 | tool_MultSelectMenu(prefix..name,menu.name,menu.bind,menu.des) 1119 | elseif menu.class == "radio" then 1120 | tool_SingleSelectMenu(prefix..name,menu.name,menu.bind,menu.des) 1121 | else 1122 | error("未知的菜单类型",2) 1123 | end 1124 | 1125 | end 1126 | 1127 | -- 自动创建菜单 1128 | --[[ 1129 | 使用符合规则的表格可自动创建 1130 | ]] 1131 | function tool_AutoMenu(name,menutable) 1132 | if type(menutable) ~= "table" then 1133 | error("创建菜单时菜单必须为合法表",2) 1134 | end 1135 | local function getNext(prefix,name,t) 1136 | if prefix ~= "" and string.sub(prefix,-1, -1) ~= "/" then 1137 | prefix = prefix.."/" 1138 | end 1139 | if string.sub(name, -2) == "/" then 1140 | name = string.sub(name, 0, -2) 1141 | end 1142 | for _,v in pairs(t) do 1143 | if type(v) ~= "table" or v.class == nil then 1144 | error("表结构不合法,菜单的值必须为表,且是合法菜单项",2) 1145 | elseif v.class == "next" then 1146 | if v.default then 1147 | if v.default.class == "menu" then 1148 | v.default.name = name 1149 | tool_applyMenu(prefix,name,v.default) 1150 | else 1151 | error("表结构不合法,默认菜单的值必须为合法menu类") 1152 | end 1153 | end 1154 | getNext(prefix..name,v.name,v.nexttable) 1155 | else 1156 | tool_applyMenu(prefix,name,v) 1157 | end 1158 | end 1159 | end 1160 | getNext("",name,menutable) 1161 | end 1162 | 1163 | -- 工具表 1164 | local tools = { 1165 | serialize = tool_serialize -- 序列化 1166 | ,unserialize = tool_unserialize -- 反序列化 1167 | ,valueToStr = valueToStr -- 将值转换为字符串 1168 | ,readIni = tool_readini -- 读取一个ini文件 1169 | ,writeIni = tool_writeini -- 写入一个ini文件 1170 | ,readConfig = tool_readConfig -- 读取插件配置 1171 | ,saveConfig = tool_saveConfig -- 更新插件配置 1172 | ,getConfig = tool_getConfig -- 读指定配置 1173 | ,setConfig = tool_setConfig -- 写指定配置 1174 | ,clearConfig = tool_clearConfig -- 清空配置 1175 | ,func_partial = functool_partial -- 创建偏函数 1176 | ,checkboxMenu = tool_MultSelectMenu -- 快速创建多选菜单(对勾菜单) 1177 | ,radioMenu = tool_SingleSelectMenu -- 快速创建单选菜单(多选一) 1178 | ,automenu = tool_AutoMenu 1179 | ,applymenu = tool_applyMenu 1180 | ,menus = { 1181 | next = tool_next 1182 | ,menu = tool_Menu 1183 | ,checkbox = tool_CheckBoxMenu 1184 | ,radio = tool_RadioMenu 1185 | ,about = tool_About 1186 | } 1187 | } 1188 | 1189 | -- table表的copy函数(来自Yutils) 1190 | function table.copy(t, depth) 1191 | -- Check argument 1192 | if type(t) ~= "table" or depth ~= nil and not(type(depth) == "number" and depth >= 1) then 1193 | error("table and optional depth expected", 2) 1194 | end 1195 | -- Copy & return 1196 | local function copy_recursive(old_t) 1197 | local new_t = {} 1198 | for key, value in pairs(old_t) do 1199 | new_t[key] = type(value) == "table" and copy_recursive(value) or value 1200 | end 1201 | return new_t 1202 | end 1203 | local function copy_recursive_n(old_t, depth) 1204 | local new_t = {} 1205 | for key, value in pairs(old_t) do 1206 | new_t[key] = type(value) == "table" and depth >= 2 and copy_recursive_n(value, depth-1) or value 1207 | end 1208 | return new_t 1209 | end 1210 | return depth and copy_recursive_n(t, depth) or copy_recursive(t) 1211 | end 1212 | 1213 | 1214 | -- 暴露的接口 1215 | --[[ 1216 | -- 此注释里的语句可将环境导入全局环境中 1217 | -- 导入 1218 | local cx_help = require"CX_AEG插件辅助函数库" 1219 | -- 合并函数环境 1220 | cx_help.table.merge(_G,cx_help) 1221 | ]] 1222 | local CX_AEG_IMP = { 1223 | -- 设置(设置默认输出等级) 1224 | setting = setting 1225 | -- AEG简写定义 1226 | ,aeg = aeg 1227 | 1228 | -- table扩展(支持表合并、表复制) 1229 | ,table = table 1230 | ,string = string 1231 | 1232 | -- 默认简化输出(支持多参数且支持nil输出) 1233 | ,print = print 1234 | ,println = println 1235 | 1236 | -- 完整输出(支持多参数,支持table输出) 1237 | ,vprint = var_print 1238 | ,vprintln = var_println 1239 | ,DD = var_println 1240 | 1241 | -- 其他环境扩展 1242 | ,randomseed = math.randomseed 1243 | ,random = math.random 1244 | 1245 | -- 简单的弹出窗口(仿JavaScript设计) 1246 | ,alert = alert 1247 | ,confirm = confirm 1248 | ,inputTextArea = inputTextArea 1249 | ,prompt = prompt 1250 | ,input = input 1251 | ,inputInt = inputInt 1252 | ,inputFloat = inputFloat 1253 | 1254 | 1255 | -- 定义退出 1256 | ,exit = aegisub.cancel 1257 | ,tools = tools -- 工具集 1258 | ,display = display -- 界面辅助 1259 | ,subsTool = subsTool -- AEG字幕行工具 1260 | ,menus = tools.menus -- 菜单快捷定义 1261 | } 1262 | -- 置入随机数种子 1263 | math.randomseed(os.time()) 1264 | 1265 | 1266 | -- 暴露的接口 1267 | return CX_AEG_IMP 1268 | 1269 | 1270 | 1271 | -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/前置库/utils-auto4.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2005-2010, Niels Martin Hansen, Rodrigo Braz Monteiro 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | * Neither the name of the Aegisub Group nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | ]] 29 | 30 | util = require 'aegisub.util' 31 | 32 | Yutils = require("Yutils") 33 | 34 | algorithm = {} 35 | shape = {} 36 | decode = {} 37 | 38 | -- shorten Yutils function name 39 | table.append = Yutils.table.append 40 | table.tostring = Yutils.table.tostring 41 | 42 | math.arc_curve = Yutils.math.arc_curve 43 | math.bezier = Yutils.math.bezier 44 | math.create_matrix = Yutils.math.create_matrix 45 | math.degree = Yutils.math.degree 46 | math.distance = Yutils.math.distance 47 | math.ortho = Yutils.math.ortho 48 | math.randomsteps = Yutils.math.randomsteps 49 | math.round = Yutils.math.round 50 | math.stretch = Yutils.math.stretch 51 | math.trim = Yutils.math.trim 52 | 53 | algorithm.frames = Yutils.algorithm.frames 54 | algorithm.lines = Yutils.algorithm.lines 55 | 56 | shape.bounding = Yutils.shape.bounding 57 | shape.detect = Yutils.shape.detect 58 | shape.filter = Yutils.shape.filter 59 | shape.flatten = Yutils.shape.flatten 60 | shape.glue = Yutils.shape.glue 61 | shape.move = Yutils.shape.move 62 | shape.split = Yutils.shape.split 63 | shape.to_outline = Yutils.shape.to_outline 64 | shape.to_pixels = Yutils.shape.to_pixels 65 | shape.transform = Yutils.shape.transform 66 | 67 | decode.create_bmp_reader = Yutils.decode.create_bmp_reader 68 | decode.create_font = Yutils.decode.create_font 69 | decode.list_fonts = Yutils.decode.list_fonts 70 | -- end 71 | 72 | 73 | 74 | table.copy = util.copy 75 | copy_line = util.copy 76 | table.copy_deep = util.deep_copy 77 | ass_color = util.ass_color 78 | ass_alpha = util.ass_alpha 79 | ass_style_color = util.ass_style_color 80 | extract_color = util.extract_color 81 | alpha_from_style = util.alpha_from_style 82 | color_from_style = util.color_from_style 83 | HSV_to_RGB = util.HSV_to_RGB 84 | HSL_to_RGB = util.HSL_to_RGB 85 | clamp = util.clamp 86 | interpolate = util.interpolate 87 | interpolate_color = util.interpolate_color 88 | interpolate_alpha = util.interpolate_alpha 89 | string.headtail = util.headtail 90 | string.trim = util.trim 91 | string.words = util.words 92 | -------------------------------------------------------------------------------- /复查助手-已发布版本存档/当前版本/更新简述.txt: -------------------------------------------------------------------------------- 1 | V2.0.1 2 | 修复了Yutils未安装时报错的问题 -------------------------------------------------------------------------------- /复查助手-已发布版本存档/更新简述.md: -------------------------------------------------------------------------------- 1 | 2 | V2.0.1 3 | 4 | 修复了Yutils未安装时报错的问题 5 | 6 | 7 | 8 | v2.0.0正式版 9 | 10 | 与之前的版本相比,大幅增强了配置功能。 11 | 12 | 并增加了格式化检测与格式化功能 13 | 14 | 可以通过配置不同的格式化文件 15 | 16 | 实现不同样式不同格式的格式化操作 17 | 18 | 降低复查或校对时的工作量 19 | 20 | 提供的统计功能也能很好的了解 21 | 22 | 当前文件的状态 23 | 24 | 25 | 26 | 基本说明 27 | 28 | 29 | 30 | 支持多功能可配置的轴文件检查 31 | 32 | 1.智能60FPS修复(视频已打开) 33 | 34 | 2.识别可能导致压制乱码的字符(包含检查当前已打开文件名) 35 | 36 | 3.识别单行字幕过长(超出视频边界) 37 | 38 | 4.识别不存在的样式 39 | 40 | 5.同样式非注释行重叠以及包含 41 | 42 | 6.识别未安装的字体(需要Yutils支持) 43 | 44 | 7.闪轴检测及修复(比旧版更好的检测联动轴) 45 | 46 | 8.检查格式,及格式化(自定义检查与替换规则,支持分样式设置) 47 | --------------------------------------------------------------------------------