├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── abandoned ├── C Centralize Drawing.lua ├── C Effect Dissolve.lua ├── C Picture Tracker.lua ├── C Scaling Rotating Conflict Solution.lua ├── C Smooth.lua ├── Effect life game.lua └── ForceTwoWindow.py ├── cheatsheet.xlsx ├── docs └── function smooth.cdf ├── index.json ├── lib ├── 0.png ├── 00000000.png ├── ColorCalibration.py ├── ForceTwoWindow2.py └── xmlSimple.lua └── src ├── C Change SUB resolution to match video PATCH.lua ├── C Color Calibration.lua ├── C Effect.lua ├── C Fast Tools.lua ├── C Font Resize.lua ├── C Gradient.lua ├── C Jump.lua ├── C Merge Bilingual SUBS.lua ├── C Replace Plus.lua ├── C Translation.lua ├── C Utilities.lua └── C XML Analyzer.lua /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Name of the script** 11 | 1. 脚本的名字 12 | 2. 版本号 13 | 14 | **Describe the bug** 15 | 在这里描述你遇到的 bug 是什么。 16 | 17 | **To Reproduce** 18 | 重现出现 bug 的过程: 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | 在这里描述你希望程序实现的功能/运行的结果... 26 | 27 | **Error Message** 28 | 将 Aegisub 的报错信息粘贴到这里 29 | 30 | **Screenshots** 31 | bug 截图 32 | 33 | **Additional context** 34 | 尽可能清楚的描述你的问题,如果不知道如何填写,可以跳过。 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **请直接无视下面的英文,直接详细描述你想要实现的功能,我会尽量给出回复** 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # password 10 | password 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | # lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.diagnostics.globals": [ 3 | "script_version", 4 | "script_author", 5 | "script_description", 6 | "script_name", 7 | "aegisub", 8 | "karaskel", 9 | "include", 10 | "lfs" 11 | ], 12 | "Lua.diagnostics.disable": [ 13 | "lowercase-global", 14 | "trailing-space", 15 | "need-check-nil" 16 | ], 17 | "Lua.workspace.checkThirdParty": false 18 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zhang-changwei 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 | # Automation-scripts-for-Aegisub 2 | [![Aegisub](https://img.shields.io/badge/Aegisub-3.2.2-blue)](https://github.com/Aegisub/Aegisub/releases/tag/v3.2.2) 3 | ![GitHub last commit](https://img.shields.io/github/last-commit/zhang-changwei/Automation-scripts-for-Aegisub) 4 | ![GitHub all releases](https://img.shields.io/github/downloads/zhang-changwei/Automation-scripts-for-Aegisub/total) 5 | ![GitHub](https://img.shields.io/github/license/zhang-changwei/Automation-scripts-for-Aegisub) 6 | ![GitHub Repo stars](https://img.shields.io/github/stars/zhang-changwei/Automation-scripts-for-Aegisub?style=social) 7 | 8 | ## __目录__ 9 | - [__前言__](#前言) 10 | - [__使用方法__](#使用方法) 11 | - [__更新日志__](#更新日志) 12 | 13 | ## __前言__ 14 | * __当前各脚本版本信息__ 15 | | Name | Version | 16 | |---------------------------------|---------| 17 | | C Change SUB resolution to match video PATCH | v1.3 | 18 | | C Color Calibration | v1.2 | 19 | | C Effect | v1.6 | 20 | | C Fast Tools | v1.2.1 | 21 | | C Font Resize (Mocha Deshaking) | v1.3 | 22 | | C Gradient | v2.2 | 23 | | C Jump | v1.0 | 24 | | C Merge Bilingual SUBS | v1.2 | 25 | | C Translation | v3.2.1 | 26 | | C Utilities | v1.7.5 | 27 | 28 | > 在Automation Manager > Description栏中查看脚本版本信息 29 | > 第二位数字表示较为重要的更新,如重要功能增加、重大bug修复等 30 | > 第三位数字表示小更新 31 | * __下载方式__ 32 | + [![Download](https://img.shields.io/badge/点此下载-orange)](https://github.com/zhang-changwei/Automation-scripts-for-Aegisub/archive/refs/heads/main.zip) 会用Github的请略。 33 | * __使用方法__ 34 | + 将LUA脚本复制到`C:\Program Files (x86)\Aegisub\automation\autoload`路径下,或你的Aegisub安装位置 35 | + 在 Aegisub Automation 项中可以发现添加的脚本 36 | + 可以在`option`中将脚本与热键绑定,建议以脚本首字母绑定热键,方便记忆 37 | * __汉化版__ 38 | + 仓库地址链接 [![Chinese](https://img.shields.io/badge/汉化版-red)](https://github.com/zhang-changwei/Automation-scripts-for-Aegisub-Chinese),感谢@章鱼哥的汉化。 39 | * __脚本依赖关系__ 40 | + `C Utilities > AE Importer > crop`依赖`imagemagick`,需自行下载,地址[https://imagemagick.org/](https://imagemagick.org/) 41 | + `C Effect & C Utilities`脚本部分功能依赖`Yutils`库,请先安装相关组件,传送门[https://github.com/Youka/Yutils](https://github.com/Youka/Yutils),感谢原作者。 42 | + `C Effect`脚本依赖`xmlSimple`库,原作者[https://github.com/Cluain/Lua-Simple-XML-Parser](https://github.com/Cluain/Lua-Simple-XML-Parser),本人作了一点修改,存放在`lib`文件夹下,将该文件放置在`C:\Program Files (x86)\Aegisub\automation\include\`目录下即可正常使用。 43 | 44 | * __该仓库本人长期维护,欢迎star与fork。__ 45 | * __cheatsheet每次发布release时更新__ 46 | 47 | ------------------------------------------- 48 | ## __使用方法__ 49 | 50 | 参考[wiki](https://github.com/zhang-changwei/Automation-scripts-for-Aegisub/wiki)页 51 | 52 | -------------------------------------------- 53 | ## __更新日志__ 54 | | Date | Script | Version | Detail | 55 | |------|--------|---------|--------| 56 | |2022.9.5|C Color Calibration|1.2|HDR->SDR| 57 | |2022.9.3|C Change SUB resolution to match video PATCH|1.3|增加舍入至3位小数| 58 | |2022.7.23|C Picture Tracker||废弃| 59 | |2022.3.13|C Merge Bilingual|1.2|增加一个更加智能的双语合并器| 60 | |2022.1.8|C Utilities|1.7.5|Dialog Checker 时间轴重叠功能改进| 61 | |2022.1.1|C Translation|3.2.1|美化界面,完善功能| 62 | |2022.1.1|C Gradient|2.2|美化界面,完善功能| 63 | |2021.11.9|C Effect|1.6|bug fix, 优化速度| 64 | |2021.11.2|C Effect|1.4|大幅更新| 65 | |2021.10.22|C Fast Tools|1.2.1|增加selection onward| 66 | |2021.10.5|C Picture Tracker|1.4.1|使用xml记忆config| 67 | |2021.9.25|C Change SUB resolution to match video PATCH|1.2|增加图片缩放适配分辨率| 68 | |2021.9.12|C Picture Tracker|1.4|修复当贴图超出边界时贴图错位| 69 | |2021.9.12|C Scaling Rotating Conflict Solution|1.1|废弃| 70 | |2021.9.12|C Effect|1.2|修复fsp宽度计算问题,加快dissolve渲染速度| 71 | |2021.9.12|C Utilities|1.7.4|界面美化,稳定dialog checker性能,兼容aegisub 3.3.0| 72 | |2021.9.5|C Change SUB resolution to match video PATCH|1.1.1|2160p分辨率尺寸写错了现已改正| 73 | |2021.9.3|C Fast Tools|1.3|增加fad序列功能| 74 | |2021.9.3|C Utilities|1.7.3|自动获取视频fps信息| 75 | |2021.9.2|C Picture Tracker|1.3|支持clip追踪| 76 | |2021.8.24|C Picture Tracker|1.2|图片追踪神器| 77 | |2021.8.24|C Utilities|1.7.2|增加批量裁剪AE图片功能| 78 | |2021.8.11|C Effect|1.1.1|bug修复| 79 | |2021.8.8|C Utilities|1.7.1|优化中文匹配,AE导入支持非从1开始的序列| 80 | |2021.8.7|Effect life game||一个小游戏| 81 | |2021.8.7|C Effect|1.1|加快运行速度,简化无用参数| 82 | |2021.8.4|C Change SUB resolution to match video PATCH|1.1|重大更新,完全重写了代码,无需经过自带的分辨率转换(精度低,有奇妙的bug),运行脚本后手动调整分辨率即可| 83 | |2021.8.2|C Fast Tools|1.1|实现按enter加\N的正常逻辑| 84 | |2021.8.2|C Jump|1.0|行间快速跳转工具| 85 | |2021.7.28|C Utilities|1.7|Move!模块加了一个move2pos按钮,使用更方便,增加删除注释行和调色(实验性)功能,修正了少量bug,加快了运行速度| 86 | |2021.7.27|C Smooth||放弃维护| 87 | |2021.7.27|C Utilities|1.6|SDH,AE Importer更新,Multiline Importer增加从剪切板导入,删掉了Tag Copy功能| 88 | |2021.7.13|C Utilities|1.5.1|摩卡可视化补上了对frz的支持| 89 | |2021.7.10|C Utilities|1.5|加入一大堆新功能| 90 | |2021.7.8|C Utilities|1.4|增添AE序列图导入功能| 91 | |2021.7.8|C Utilities|1.3|增加进度条显示,进一步细分双语checker,改变部分逻辑,加快运行速度,修正了一些bug| 92 | |2021.6.24|C Translation & Gradient| |将`math.power`替换为`^`,以兼容LUA 5.4| 93 | |2021.6.24|C Translation|3.2|乘法for fscx fscy| 94 | |2021.6.24|C Utilities|1.2|功能更新for buffer| 95 | |2021.4.20|C Font Resize|1.3|增加对矢量图的支持| 96 | |2021.4.20|C Effect|1.0|beta 内测版| 97 | |2021.3.20|C Scaling Rotation Conflict Solution|1.1|Bug Fixed| 98 | |2021.3.2|C Translation|3.1|解决字体中"W"导致错误| 99 | |2021.3.1|C Gradient|2.1|解决字体中"W"导致错误,修复 `1vc` 中的 bug,新增对 `t1(\t第1个参数),t2(\t第2个参数),[i]clip` 的支持| 100 | |2021.2.28|C Font Resize|1.2|解决字体中"W"导致错误,增添对样式表中设置fsp值的支持| 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /abandoned/C Centralize Drawing.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | Put some explanation about your macro at the top! People should know what it does and how to use it. 5 | ]] 6 | 7 | script_name="C Centralize Drawing" 8 | script_description="Centralize Drawing" 9 | script_author="chaaaaang" 10 | script_version="1.0" 11 | 12 | --This is the main processing function that modifies the subtitles 13 | function Centralization(subtitle, selected, active) 14 | 15 | for si,li in ipairs(selected) do 16 | line=subtitle[li] 17 | 18 | if (line.text:match("\\pos")==nil) then line.text=line.text:gsub("^{","{\\pos(0,0)") end 19 | if (line.text:match("\\an")==nil) then line.text=line.text:gsub("^{","{\\an7") end 20 | 21 | line.text=line.text:gsub("\\fsc[%d%.%-]+","") 22 | line.text=line.text:gsub("\\fscx[%d%.%-]+","") 23 | line.text=line.text:gsub("\\fscy[%d%.%-]+","") 24 | line.text=line.text:gsub("\\an%d","\\an7") 25 | line.text=line.text:gsub("\\pos%([^%)]+%)","\\pos(0,0)\\fscx100\\fscy100") 26 | 27 | pos = string.match(line.text, "m +%-?%d+.*") 28 | i = 1 29 | totalx = 0 30 | totaly = 0 31 | posx = {} 32 | posy = {} 33 | for xy in string.gmatch(pos, "%-?%d+%.?%d*") do 34 | i = i + 1 35 | index = math.floor(i/2.0) 36 | chose = i%2 37 | if (chose == 0) then 38 | posx[index] = tonumber(xy) 39 | --totalx = totalx + tonumber(xy) 40 | if (index == 1) then 41 | x_max=tonumber(xy) 42 | x_min=tonumber(xy) 43 | end 44 | if (tonumber(xy)>x_max) then x_max=tonumber(xy) end 45 | if (tonumber(xy)y_max) then y_max=tonumber(xy) end 54 | if (tonumber(xy)fi2) then local temp=fi1 fi1=fi2 fi2=temp end 197 | return string.format("{\\alpha&HFF&\\t(%d,%d,\\alpha&H00&)",fi1,fi2) 198 | end) 199 | 200 | lgtext = lgtext:gsub("^({[^}]*)}", 201 | function(b) 202 | local fo1 = math.random(0,math.floor(fout_time)) 203 | local fo2 = math.random(0,math.floor(fout_time)) 204 | if (fo1>fo2) then local temp=fo1 fo1=fo2 fo2=temp end 205 | return string.format("%s\\t(%d,%d,\\alpha&HFF&)}",b, line.duration-fo2, line.duration-fo1) 206 | end) 207 | lg.text = lgtext 208 | subtitle[li] = lg 209 | end 210 | end 211 | end 212 | 213 | aegisub.set_undo_point(script_name) 214 | return selected 215 | end 216 | 217 | --Register macro (no validation function required) 218 | aegisub.register_macro(script_name,script_description,main) 219 | -------------------------------------------------------------------------------- /abandoned/C Picture Tracker.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 5 | 6 | get info: magick identify path1 7 | 8 | get info: magick path1 -identify output.txt 9 | 10 | corp: magick path1 -crop 100x50+100+20 +repage path2 11 | 12 | rotate: magick path1 -fill rgba(255,255,255,255) -background rgba(0,0,0,0) -rotate "45>" path2 旋转方向相反 >: w>h 13 | 14 | shrink edge: magick path1 -trim +repage path2 15 | 16 | get info: ffprobe -i path1 -show_streams > otput.txt 17 | 18 | ]] 19 | 20 | --Script properties 21 | script_name="C Picture Tracker" 22 | script_description="Picture Tracker v1.4.1" 23 | script_author="chaaaaang" 24 | script_version="1.4.1" 25 | 26 | include("karaskel.lua") 27 | clipboard = require 'aegisub.clipboard' 28 | 29 | local dialog_config = { 30 | {class="label",label="",x=0,y=0,width=4},--1 31 | {class="label",label="reference frame",x=1,y=1},--2 32 | {class="intedit",name="f",value=1,x=2,y=1,width=2},--3 33 | {class="label",label="vertial shrink",x=1,y=2},--4 34 | {class="intedit",name="vs",value=0,x=2,y=2,width=2},--5 35 | {class="label",label="horizontal shrink",x=1,y=3},--6 36 | {class="intedit",name="hs",value=0,x=2,y=3,width=2},--7 37 | 38 | {class="checkbox",name="x",label="pos",value=true,x=0,y=1}, 39 | {class="checkbox",name="s",label="scale",value=false,x=0,y=2}, 40 | {class="checkbox",name="r",label="rotate",value=false,x=0,y=3}, 41 | {class="checkbox",name="cl",label="clip",value=false,x=0,y=4}, 42 | {class="checkbox",name="r2v",label="rect2vec",value=false,x=1,y=4}, 43 | {class="checkbox",name="text",label="text ",value=false,x=2,y=4}, 44 | {class="checkbox",name="rem",label="rem",value=false,x=3,y=4}, 45 | 46 | {class="label",label="effect",x=0,y=5}, 47 | {class="dropdown",name="e",items={"none","paint, recommend: 5","spread, recommend: 5,5","swirl, recommend 360","custom command"},value="none",x=0,y=6,width=2}, 48 | {class="dropdown",name="m",items={"gradient","random"},value="gradient",x=2,y=6,width=2}, 49 | {class="edit",name="c",x=0,y=7,width=4,value="custom command:",hint="custom command: use input & output for file path, use \\d for argument"}, 50 | {class="label",label="strength from/min",x=0,y=8,width=2}, 51 | {class="edit",name="argf",x=2,y=8,value="0",width=2,hint="strength for preview, separate by comma"}, 52 | {class="label",label="strength to/max",x=0,y=9,width=2}, 53 | {class="edit",name="argt",x=2,y=9,value="0",width=2,hint="separate by comma"} 54 | } 55 | local buttons = {"Track","Preview","Quit"} 56 | 57 | function main(subtitle,selected,active) 58 | local meta,styles=karaskel.collect_head(subtitle,false) 59 | local xres, yres, ar, artype = aegisub.video_size() 60 | -- first get png path 61 | local path = subtitle[active].text:match("\\1img%(([^,%)]+)") 62 | path = path:gsub("/","\\") 63 | local path_head = path:gsub("%.png","") 64 | 65 | -- read width & height 66 | local cmdinit = string.format('magick %s -format "%%wx%%h" info:',path) 67 | local file = io.popen(cmdinit) 68 | local info = file:read() 69 | file:close() 70 | local width,height = info:match("(%d+)x(%d+)") 71 | width,height = tonumber(width),tonumber(height) 72 | local wlh = (width>=height) and ">" or "<" 73 | 74 | 75 | -- get mocha data 76 | local mochatext = clipboard.get() 77 | local count_m,trigger = 0,0 78 | local fps 79 | local posdata, scaledata, rotationdata = {},{},{} 80 | if mochatext~=nil then 81 | mochatext = mochatext.."\n" 82 | for i in mochatext:gmatch("(.-)\n") do 83 | if i=="End of Keyframe Data" then break end 84 | 85 | if i:match("Units Per Second") then 86 | fps = i:match("[%d%.]+") 87 | fps = tonumber(fps) 88 | end 89 | 90 | if trigger==1 and i:match("%d")~=nil then 91 | count_m = count_m + 1 92 | local x,y = i:match("%d+\t([%d%.%-e]+)\t([%d%.%-e]+)") 93 | x,y = tonumber(x),tonumber(y) 94 | table.insert(posdata,{x=x,y=y}) 95 | end 96 | if trigger==2 and i:match("%d")~=nil then 97 | local x = i:match("%d+\t([%d%.%-e]+)") 98 | x = tonumber(x) 99 | table.insert(scaledata,x) 100 | end 101 | if trigger==3 and i:match("%d")~=nil then 102 | local x = i:match("%d+\t([%d%.%-e]+)") 103 | x = tonumber(x) 104 | table.insert(rotationdata,x) 105 | end 106 | 107 | if i:match("Rotation")~=nil then trigger=3 end 108 | if i:match("Scale")~=nil then trigger=2 end 109 | if i:match("Position")~=nil then trigger=1 end 110 | end 111 | else count_m = 0 112 | end 113 | if posdata=={} then count_m = 0 end 114 | 115 | -- get line info 116 | local frame_S,frame_E = aegisub.frame_from_ms(subtitle[active].start_time),aegisub.frame_from_ms(subtitle[active].end_time)-1 117 | local count_f = frame_E + 1 - frame_S 118 | 119 | --UI 120 | config_read_xml(dialog_config) 121 | dialog_config[1].label = "expected frame count: "..count_f..", mocha frame count: "..count_m 122 | dialog_config[3].value = aegisub.project_properties().video_position 123 | local pressed, result = aegisub.dialog.display(dialog_config,buttons) 124 | if pressed=="Quit" then aegisub.cancel() end 125 | if result.rem==true then config_write_xml(result) end 126 | 127 | -- fps correction 128 | if fps==23.976 then fps = 24000/1001 129 | elseif fps==29.97 then fps = 30000/1001 end 130 | 131 | if pressed=="Track" then 132 | if count_f~=count_m then aegisub.cancel() end 133 | -- ref frame 134 | local frame_ref = result.f-frame_S+1 135 | local line = subtitle[active] 136 | karaskel.preproc_line(subtitle,meta,styles,line) 137 | line.comment = true 138 | subtitle[active] = line 139 | line.comment = false 140 | local tag = line.text:match("^{[^}]*}") 141 | tag = tag:gsub("\\1img%([^%)]*%)","") 142 | tag = tag:gsub("\\fsc([%d%.]+)","\\fscx%1\\fscy%1") 143 | local tag_strip_t = tag:gsub("\\t%([^%)]*%)","") 144 | local align = tag:match("\\an%d") and tag:match("\\an(%d)") or line.styleref.align 145 | local angle = tag_strip_t:match("\\frz") and tag_strip_t:match("\\frz([%d%.%-]+)") or line.styleref.angle 146 | local scale_x = tag_strip_t:match("\\fscx") and tag_strip_t:match("\\fscx([%d%.]+)") or line.styleref.scale_x 147 | local scale_y = tag_strip_t:match("\\fscy") and tag_strip_t:match("\\fscy([%d%.]+)") or line.styleref.scale_y 148 | local posx,posy = tag:match("\\pos%(([%d%.%-]+),([%d%.%-]+)") 149 | local orgx,orgy = tag:match("\\org%(([%d%.%-]+),([%d%.%-]+)") 150 | local fin,fout = tag:match("\\fad%(([%d%.%-]+),([%d%.%-]+)") 151 | local clip_head,clip_shape = tag_strip_t:match("(\\i?clip)%(([^%)]*)%)") 152 | local alpha = tag_strip_t:match("\\1?al?p?h?a?&?H?%x") and "&H"..tag_strip_t:match("\\1?al?p?h?a?&?H?([%x]+)&?").."&" or "&H"..line.styleref.color1:match("(%x%x)%x%x%x%x%x%x").."&" 153 | if posx==nil then posx,posy = line.x,line.y end 154 | align,posx,posy,angle,scale_x,scale_y = tonumber(align),tonumber(posx),tonumber(posy),tonumber(angle),tonumber(scale_x),tonumber(scale_y) 155 | if orgx~=nil then orgx,orgy = tonumber(orgx),tonumber(orgy) end 156 | if fin~=nil then fin,fout = tonumber(fin),tonumber(fout) end 157 | -- for shape 158 | local tagforshape = "{\\an"..align.."\\bord0\\shad0\\fscx100\\fscy100\\frz0\\p1" 159 | -- for text 160 | local linetext = line.text:match("^{")~=nil and line.text or "{}"..line.text 161 | linetext = linetext:gsub("\\t%(([^,%)]+)%)",function (a) return string.format("\\t(0,%d,%s)",line.duration,a) end) 162 | linetext = linetext:gsub("\\fad%([^%)]+%)","") 163 | linetext = linetext:gsub("\\fsc([%d%.]+)","\\fscx%1\\fscy%1") 164 | if fin~=nil and fin~=0 then linetext = linetext:gsub("^{",string.format("{\\alpha&HFF&\\t(0,%d,\\alpha%s)",fin,alpha)) end 165 | if fout~=nil and fout~=0 then linetext = linetext:gsub("^({[^}]*)}",function(a) return string.format("%s\\t(%d,%d,\\alpha&HFF&)",a,line.duration-fout,line.duration) end) end 166 | 167 | -- clip rect2vec 168 | if result.cl==true and result.r2v==true and clip_shape:match(",")~=nil then 169 | local cx1,cy1,cx2,cy2 = clip_shape:match("([^,]+),([^,]+),([^,]+),([^,]+)") 170 | cx1,cy1,cx2,cy2 = tonumber(cx1),tonumber(cy1),tonumber(cx2),tonumber(cy2) 171 | clip_shape = string.format("m %.1f %.1f l %.1f %.1f %.1f %.1f %.1f %.1f",cx1,cy1, cx2,cy1, cx2,cy2, cx1,cy2) 172 | end 173 | 174 | -- effect 175 | local argfs,argts = {},{} 176 | local command0 177 | if result.e~="none" then 178 | if result.e=="paint, recommend: 5" then 179 | command0 = "-paint \\d " 180 | elseif result.e=="spread, recommend: 5,5" then 181 | command0 = "-blur \\d -spread \\d " 182 | elseif result.e=="swirl, recommend 360" then 183 | command0 = "-swirl \\d " 184 | elseif result.e=="custom command" then 185 | command0 = result.c:gsub("^ *magick +input +","") 186 | command0 = command0:gsub("output *$","") 187 | end 188 | 189 | -- args 190 | local argtext = result.argf.."," 191 | for i in argtext:gmatch("([^,]+),") do 192 | i = tonumber(i) 193 | table.insert(argfs,i) 194 | end 195 | argtext = result.argt.."," 196 | for i in argtext:gmatch("([^,]+),") do 197 | i = tonumber(i) 198 | table.insert(argts,i) 199 | end 200 | end 201 | 202 | -- write 203 | local j = active 204 | local index = 1 205 | for i=frame_S,frame_E do 206 | subtitle.insert(j,line) 207 | local l = subtitle[j] 208 | l.start_time = starttime(i,fps) 209 | l.end_time = endtime(i,fps) 210 | 211 | -- do the picture stuff 212 | local draw,drawpos,draworg,drawclip = "","","","" 213 | local path_o = path_head.."_"..i..".png" 214 | local w,h = width,height 215 | local xdev, ydev, rotdev, scadev, timedev = posx-posdata[frame_ref].x, posy-posdata[frame_ref].y, 0, 1, l.start_time-line.start_time 216 | local posxN, posyN, orgxN, orgyN = posx, posy, orgx, orgy 217 | 218 | -- scale & rotation 219 | if result.s==true or result.r==true then 220 | local command1 = string.format('magick %s ',path) 221 | if result.s==true then 222 | scadev = scaledata[index]/scaledata[frame_ref] 223 | command1 = command1..string.format('-resize %f%% ', scadev*100) 224 | xdev,ydev = xdev*scadev,ydev*scadev 225 | end 226 | if result.r==true then 227 | rotdev = rotationdata[index]-rotationdata[frame_ref] 228 | command1 = command1..string.format('-fill rgba(255,255,255,255) -background rgba(0,0,0,0) -rotate "%f%s" +repage ',rotdev, wlh) 229 | end 230 | 231 | -- effect 232 | if result.e~="none" then 233 | local command0copy = command0 234 | for k=1,#argfs do 235 | if result.m=="gradient" then 236 | command0copy = command0copy:gsub("\\d", interpolate(argfs[k],argts[k],count_f,index,1),1) 237 | else 238 | local argmin,argmax = math.min(argfs[k],argts[k]),math.max(argfs[k],argts[k]) 239 | command0copy = command0copy:gsub("\\d", math.random()*(argmax-argmin)+argmin,1) 240 | end 241 | end 242 | command1 = command1.." "..command0copy 243 | end 244 | 245 | command1 =command1..path_o 246 | os.execute(command1) 247 | if result.text==false then 248 | local command2 = string.format('magick %s -format "%%wx%%h" info:',path_o) 249 | file = io.popen(command2) 250 | info = file:read() 251 | file:close() 252 | w,h = info:match("(%d+)x(%d+)") 253 | w,h = tonumber(w),tonumber(h) 254 | end 255 | else 256 | path_o = path 257 | 258 | -- effect 259 | if result.e~="none" then 260 | path_o = path_head.."_"..i..".png" 261 | local command0copy = command0 262 | for k=1,#argfs do 263 | if result.m=="gradient" then command0copy = command0copy:gsub("\\d", interpolate(argfs[k],argts[k],count_f,index),1) 264 | else 265 | local argmin,argmax = math.min(argfs[k],argts[k]),math.max(argfs[k],argts[k]) 266 | command0copy = command0copy:gsub("\\d", math.random()*(argmax-argmin)+argmin,1) 267 | end 268 | end 269 | command1 = 'magick '..path.." "..command0copy..path_o 270 | os.execute(command1) 271 | end 272 | end 273 | 274 | -- position 275 | if result.x==true then 276 | posN, posyN = posdata[index].x+xdev, posdata[index].y+ydev 277 | drawpos = string.format("\\pos(%.3f,%.3f)",posxN, posyN) 278 | else 279 | drawpos = "\\pos("..posx..","..posy..")" 280 | end 281 | 282 | -- org 283 | if orgx~=nil then 284 | if result.x==true then 285 | local orgxdev, orgydev = orgx-posdata[frame_ref].x, orgy-posdata[frame_ref].y 286 | if result.r==true then orgxdev,orgydev = orgxdev*scadev,orgydev*scadev end 287 | orgxN, orgyN = posdata[index].x+orgxdev, posdata[index].y+orgydev 288 | draworg = string.format("\\org(%.3f,%.3f)",orgxN, orgyN) 289 | else 290 | draworg = "\\org("..orgx..","..orgy..")" 291 | end 292 | end 293 | 294 | -- clip 295 | if result.cl==true then 296 | local clip_shape_copy = clip_shape 297 | -- clip position 298 | if result.x==true then 299 | clip_shape_copy = filter(clip_shape_copy, posdata[index].x, posdata[index].y, posdata[frame_ref].x, posdata[frame_ref].y, scadev) 300 | end 301 | -- clip scale 302 | if result.s==true and orgx~=nil then 303 | clip_shape_copy =filter_s(clip_shape_copy, orgxN, orgyN, scadev) 304 | elseif result.s==true then 305 | clip_shape_copy =filter_s(clip_shape_copy, posxN, posyN, scadev) 306 | end 307 | -- clip rotation 308 | if result.r==true and orgx~=nil then 309 | clip_shape_copy =filter_r(clip_shape_copy, orgxN, orgyN, rotdev) 310 | elseif result.r==true then 311 | clip_shape_copy =filter_r(clip_shape_copy, posxN, posyN, rotdev) 312 | end 313 | drawclip = clip_head.."("..clip_shape_copy..")" 314 | elseif clip_shape~=nil then 315 | drawclip = clip_head.."("..clip_shape..")" 316 | end 317 | 318 | -- handle picture out of boundry 319 | local bottom = getbottom(posdata[index].y+ydev, h, align) 320 | if bottom>yres then 321 | local path_o2 = path_head.."_"..i..".png" 322 | local h_temp = math.floor(h-(bottom-yres)) 323 | if h_temp>result.vs then 324 | h = h_temp 325 | local commandcrop = string.format("magick %s -crop %dx%d+0+0 +repage %s",path_o,w,h,path_o2) 326 | os.execute(commandcrop) 327 | path_o = path_o2 328 | else 329 | l.comment = true 330 | end 331 | end 332 | 333 | -- output 334 | if result.text==false then 335 | draw = string.format("%s%s%s%s\\1img(%s)}m 0 0 l %d 0 l %d %d l 0 %d", 336 | tagforshape, drawpos, draworg, drawclip, path_o, w-result.hs, w-result.hs, h-result.vs, h-result.vs) 337 | l.text = draw 338 | else 339 | local ltcopy = linetext 340 | if result.x==true then 341 | ltcopy = ltcopy:gsub("\\pos%([^%)]+%)","") 342 | ltcopy = ltcopy:gsub("^{", "{"..drawpos) 343 | end 344 | if orgx~=nil then 345 | ltcopy = ltcopy:gsub("\\org%([^%)]+%)","") 346 | ltcopy = ltcopy:gsub("^{", "{"..draworg) 347 | end 348 | if result.r==true then 349 | if ltcopy:match("\\frz")==nil then ltcopy = ltcopy:gsub("^{","{\\frz"..angle) end 350 | ltcopy = ltcopy:gsub("\\frz([%d%.%-]+)", string.format("\\frz%.3f", angle-rotdev)) 351 | end 352 | if result.s==true then 353 | if ltcopy:match("\\fscy")==nil then ltcopy = ltcopy:gsub("^{","{\\fscy"..scale_y) end 354 | if ltcopy:match("\\fscx")==nil then ltcopy = ltcopy:gsub("^{","{\\fscx"..scale_x) end 355 | ltcopy = ltcopy:gsub("\\fscx([%d%.]+)", string.format("\\fscx%.2f", scale_x*scadev)) 356 | ltcopy = ltcopy:gsub("\\fscy([%d%.]+)", string.format("\\fscy%.2f", scale_y*scadev)) 357 | end 358 | if result.cl==true then 359 | ltcopy = ltcopy:gsub("^({[^}]*)}",function (a) return a..drawclip.."}" end) 360 | end 361 | ltcopy = ltcopy:gsub("\\t%(([^,]+),([^,]+),([^%)]*)%)", function (t1,t2,a) 362 | t1,t2 = tonumber(t1)-timedev,tonumber(t2)-timedev 363 | if t2<=0 then return a 364 | else return string.format("\\t(%d,%d,%s)",t1,t2,a) 365 | end 366 | end) 367 | l.text = ltcopy 368 | end 369 | subtitle[j] = l 370 | aegisub.progress.set(index/count_f*100) 371 | j = j + 1 372 | index = index + 1 373 | end 374 | 375 | elseif pressed=="Preview" then 376 | local command0 377 | if result.e=="none" then aegisub.cancel() 378 | elseif result.e=="paint, recommend: 5" then 379 | command0 = "magick input -paint \\d output" 380 | elseif result.e=="spread, recommend: 5,5" then 381 | command0 = "magick input -blur \\d -spread \\d output" 382 | elseif result.e=="swirl, recommend 360" then 383 | command0 = "magick input -swirl \\d output" 384 | elseif result.e=="custom command" then 385 | command0 = result.c 386 | end 387 | 388 | -- args 389 | local args = {} 390 | local argtext = result.argf.."," 391 | for i in argtext:gmatch("([^,]+),") do 392 | i = tonumber(i) 393 | table.insert(args,i) 394 | end 395 | 396 | -- IM command 397 | local path_o = path_head.."_pre.png" 398 | command0 = command0:gsub("input",path) 399 | command0 = command0:gsub("output",path_o) 400 | for i=1,#args do 401 | command0 = command0:gsub("\\d",args[i],1) 402 | end 403 | command1 = string.format('magick %s -format "%%wx%%h" info:',path_o) 404 | os.execute(command0) 405 | file = io.popen(command1) 406 | info = file:read() 407 | file:close() 408 | width,height = info:match("(%d+)x(%d+)") 409 | width,height = tonumber(width),tonumber(height) 410 | 411 | -- output 412 | local line = subtitle[active] 413 | line.text = line.text:gsub("%.png","_pre.png") 414 | line.text = line.text:gsub("}.-$",string.format("}m 0 0 l %d 0 %d %d 0 %d",width,width,height,height)) 415 | subtitle[active] = line 416 | end 417 | 418 | aegisub.set_undo_point(script_name) 419 | return selected 420 | end 421 | 422 | function getbottom(posy, height, align) 423 | if align==1 or align==2 or align==3 then 424 | return posy 425 | elseif align==4 or align==5 or align==6 then 426 | return posy + height/2 427 | elseif align==7 or align==8 or align==9 then 428 | return posy + height 429 | end 430 | end 431 | 432 | function starttime(f,fps) 433 | return math.floor((f-0.5)*(1000/fps)/10)*10 434 | end 435 | 436 | function endtime(f,fps) 437 | return math.floor((f+0.5)*(1000/fps)/10)*10 438 | end 439 | 440 | function round(x) 441 | return math.floor(x+0.5) 442 | end 443 | 444 | function interpolate(head,tail,N,i,accel) 445 | -- i 1-N 446 | if accel==nil then accel = 1 end 447 | local bias = (1/(N-1)*(i-1))^accel 448 | return (tail-head)*bias+head 449 | end 450 | 451 | -- scale = 1 452 | function filter(shape, xn, yn, xref, yref, scale) 453 | local s = "" 454 | for p,x,y in shape:gmatch("([^%d%.%-]+)([%d%.%-]+) +([%d%.%-]+)") do 455 | x,y = tonumber(x),tonumber(y) 456 | local xdev,ydev = (x-xref)*scale,(y-yref)*scale 457 | s = s..p..string.format("%.1f %.1f", xn+xdev, yn+ydev) 458 | end 459 | return s 460 | end 461 | 462 | function filter_s(shape, cx, cy, scale) 463 | local s = "" 464 | for p,x,y in shape:gmatch("([^%d%.%-]+)([%d%.%-]+) +([%d%.%-]+)") do 465 | x,y = tonumber(x),tonumber(y) 466 | local xdev,ydev = (x-cx)*scale,(y-cy)*scale 467 | s = s..p..string.format("%.1f %.1f", cx+xdev, cy+ydev) 468 | end 469 | return s 470 | end 471 | 472 | function filter_r(shape, cx, cy, theta) 473 | local s = "" 474 | for p,x,y in shape:gmatch("([^%d%.%-]+)([%d%.%-]+) +([%d%.%-]+)") do 475 | x,y = tonumber(x),tonumber(y) 476 | x,y = rotation(x,y,cx,cy,theta) 477 | s = s..p..string.format("%.1f %.1f", x, y) 478 | end 479 | return s 480 | end 481 | 482 | function rotation(x, y, cx, cy, theta) 483 | theta = math.rad(theta) 484 | local r = math.sqrt((x-cx)^2+(y-cy)^2) 485 | if r<0.0001 then return x,y end 486 | -- cos(t1+t2) = cost1 *cost2 - sint1 *sint2 487 | -- sin(t1+t2) = sint1 *cost2 + cost1 *sint2 488 | local cost = (x-cx)/r * math.cos(theta) - (y-cy)/r * math.sin(theta) 489 | local sint = (y-cy)/r * math.cos(theta) + (x-cx)/r * math.sin(theta) 490 | return r*cost+cx, r*sint+cy 491 | end 492 | 493 | function config_read_xml(dialog) 494 | local path = aegisub.decode_path("?user").."\\picture_tracker_config.xml" 495 | local file = io.open(path, "r") 496 | if file~=nil then 497 | file:close() 498 | local config = require("xmlSimple").newParser():loadFile(path) 499 | for si,li in ipairs(dialog) do 500 | for sj,lj in pairs(li) do 501 | if sj=="class" and lj~="label" then 502 | local name = li.name 503 | local item = config.Config[name] 504 | if item["@Type"]=="boolean" then 505 | dialog[si].value = str2bool(item["@Value"]) 506 | elseif item["@Type"]=="number" then 507 | dialog[si].value = tonumber(item["@Value"]) 508 | elseif item["@Type"]=="string" then 509 | dialog[si].value = item["@Value"] 510 | end 511 | break 512 | end 513 | end 514 | end 515 | else 516 | return nil 517 | end 518 | end 519 | 520 | function config_write_xml(result) 521 | local path = aegisub.decode_path("?user").."\\picture_tracker_config.xml" 522 | local file = io.open(path, "w") 523 | file:write('\n\n') 524 | for key,value in pairs(result) do 525 | if type(value)=="boolean" then 526 | file:write(string.format('<%s Type="%s" Value="%s"/>\n', key, type(value), bool2str(value))) 527 | else 528 | file:write(string.format('<%s Type="%s" Value="%s"/>\n', key, type(value), value)) 529 | end 530 | end 531 | file:write('') 532 | file:close() 533 | end 534 | 535 | function str2bool(str) 536 | if str=="true" then return true 537 | else return false end 538 | end 539 | 540 | function bool2str(bool) 541 | if bool==true then return "true" 542 | else return "false" end 543 | end 544 | 545 | --Register macro (no validation function required) 546 | aegisub.register_macro(script_name,script_description,main) 547 | 548 | --[[ 549 | frosted 550 | disperse 551 | mottle 552 | peelingpaint 553 | pixelize 554 | ripples 555 | sketch 556 | stainedglass 557 | vintage 558 | 559 | -blur 5 -spread 5 560 | -paint 5 561 | -swirl 180 562 | -charcoal 1 563 | 564 | aegisub.project_properties() ->table 565 | export_encoding 566 | timecodes_file 567 | audio_file 568 | ar_mode 0 569 | ar_value 1.7777777777778 570 | style_storage 571 | active_row 0 572 | automation_scripts 573 | video_file ?dummy:23.976000:400000:1920:1080:0:0:0: 574 | video_zoom 0.5 575 | keyframes_file 576 | video_position 6 577 | scroll_position 0 578 | export_filters 579 | ]] -------------------------------------------------------------------------------- /abandoned/C Scaling Rotating Conflict Solution.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | ]] 5 | 6 | --Script properties 7 | script_name="C Scaling Rotation Conflict Solution" 8 | script_description="Scaling Rotation Conflict Solution v1.1" 9 | script_author="chaaaaang" 10 | script_version="1.1" 11 | 12 | include("karaskel.lua") 13 | 14 | --GUI 15 | dialog_config={ 16 | {class="label",label="suffix",x=0,y=0}, 17 | {class="intedit",name="suffix",value=1,x=0,y=1} 18 | } 19 | buttons={"Run","Quit"} 20 | 21 | function main(subtitle, selected) 22 | local meta,styles=karaskel.collect_head(subtitle,false) 23 | 24 | pressed, result = aegisub.dialog.display(dialog_config,buttons) 25 | if (pressed=="Quit") then aegisub.cancel() end 26 | 27 | --get style_name and table 28 | selected_style = "" 29 | style_table = {} 30 | for si,li in ipairs(selected) do 31 | 32 | local line=subtitle[li] 33 | karaskel.preproc_line(subtitle,meta,styles,line) 34 | 35 | local ltext=(line.text:match("^{")==nil) and "{}"..line.text or line.text 36 | 37 | ltext = ltext:gsub("\\fsc([%d%-%.]+)","\\fscx%1\\fscy%1") 38 | 39 | if (ltext:match("\\fscx")==nil) then ltext = ltext:gsub("^{",string.format("{\\fscx%.2f"),line.styleref.scale_x) end 40 | if (ltext:match("\\fscy")==nil) then ltext = ltext:gsub("^{",string.format("{\\fscy%.2f"),line.styleref.scale_y) end 41 | 42 | local scale_x = ltext:match("\\fscx([%d%-%.]+)") 43 | local scale_y = ltext:match("\\fscy([%d%-%.]+)") 44 | ltext = ltext:gsub("\\fscx[%d%-%.]+","") 45 | ltext = ltext:gsub("\\fscy[%d%-%.]+","") 46 | 47 | selected_style = line.style 48 | line.style = string.format("%s_%d",line.style,result["suffix"]) 49 | line.text = ltext 50 | 51 | table.insert(style_table,{name=line.style,x=scale_x,y=scale_y}) 52 | 53 | subtitle[li]=line 54 | 55 | result["suffix"] = result["suffix"] + 1 56 | end 57 | --search from first line 58 | for li=1,#subtitle do 59 | local style = subtitle[li] 60 | if (style.class == "style" and style.name == selected_style) then 61 | -- subtitle.delete(li) 62 | for _,sty in ipairs(style_table) do 63 | subtitle.insert(li,style) 64 | sg = subtitle[li] 65 | sg.name = sty.name 66 | sg.scale_x = sty.x 67 | sg.scale_y = sty.y 68 | subtitle[li] = sg 69 | 70 | li = li + 1 71 | end 72 | break 73 | end 74 | end 75 | aegisub.set_undo_point(script_name) 76 | return 0 77 | end 78 | 79 | --Register macro (no validation function required) 80 | aegisub.register_macro(script_name,script_description,main) 81 | -------------------------------------------------------------------------------- /abandoned/C Smooth.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | 5 | ]] 6 | 7 | script_name="C Smooth" 8 | script_description="Smooth v1.0" 9 | script_author="chaaaaang" 10 | script_version="1.0" 11 | 12 | include("karaskel.lua") 13 | require("lfs") 14 | 15 | function main(subtitle, selected, active) 16 | local meta,styles=karaskel.collect_head(subtitle,false) 17 | 18 | --count the size N 19 | local i = 0 20 | 21 | for sa,la in ipairs(selected) do 22 | local line = subtitle[la] 23 | local ltext=(line.text:match("^{")==nil) and "{}"..line.text or line.text 24 | ltext=ltext:gsub("}{","") 25 | line.text = ltext 26 | subtitle[la] = line 27 | i = i + 1 28 | end 29 | local N = i 30 | --end count the size N 31 | config_table = config_read() 32 | 33 | --GUI 34 | dialog_config={ 35 | {class="dropdown",name="tagtype",items={"posx","posy","fscx","fscy","frz"},value=config_table.tag,x=0,y=0}, 36 | {class="label",label="index",x=1,y=0}, 37 | {class="intedit",name="index",value=config_table.index,x=2,y=0}, 38 | --100 percent 39 | {class="label",label="lower threshold",x=0,y=1}, 40 | {class="floatedit",name="lower_threshold",value=config_table.L,x=1,y=1,hint="default: 180"}, 41 | {class="checkbox",name="keep_L",label="keep checking lower_threshold",value=config_table.keep_L,x=2,y=1,width=2}, 42 | 43 | {class="label",label="upper threshold",x=0,y=2}, 44 | {class="floatedit",name="upper_threshold",value=config_table.U,x=1,y=2,hint="default: 500"}, 45 | {class="checkbox",name="keep_U",label="keep checking upper_threshold",value=config_table.keep_U,x=2,y=2,width=2}, 46 | --force 0-100 47 | {class="label",label="force",x=0,y=3}, 48 | {class="floatedit",name="force",value=config_table.F,x=1,y=3,hint="default: 30"}, 49 | {class="checkbox",name="remember",label="remember config",value=config_table.remember,x=2,y=3,width=2} 50 | } 51 | buttons={"Run","Quit"} 52 | 53 | pressed, result = aegisub.dialog.display(dialog_config,buttons) 54 | if (pressed=="Quit") then aegisub.cancel() end 55 | 56 | if (pressed=="Run") then 57 | if (result["remember"]==true) then config_write(result) end 58 | result["upper_threshold"] = result["upper_threshold"]/100 59 | result["lower_threshold"] = result["lower_threshold"]/100 60 | result["force"] = result["force"]/100 61 | tag_table={} 62 | 63 | --get the tag information and write it to a table 64 | if (result["tagtype"]=="posx" or result["tagtype"]=="posy") then 65 | local i = 0 66 | for si,li in ipairs(selected) do 67 | i = i + 1 68 | local line = subtitle[li] 69 | if (result["tagtype"]=="posx") then 70 | local posx = tonumber(line.text:match("\\pos%(([%d%.%-]+)")) 71 | table.insert(tag_table,{index=i,value=posx,change=""}) 72 | else 73 | local posy = tonumber(line.text:match("\\pos%([%d%.%-]+,([%d%.%-]+)")) 74 | table.insert(tag_table,{index=i,value=posy,change=""}) 75 | end 76 | end 77 | elseif (result["tagtype"]=="fscx" or result["tagtype"]=="fscy" or result["tagtype"]=="frz") then 78 | local i = 0 79 | for si,li in ipairs(selected) do 80 | i = i + 1 81 | local line = subtitle[li] 82 | local tt_table={} 83 | for tg,tx in line.text:gmatch("({[^}]*})([^{]*)") do 84 | table.insert(tt_table,{tag=tg,text=tx}) 85 | end 86 | 87 | local count = 0 88 | for _,tt in ipairs(tt_table) do 89 | tt.tag = tt.tag:gsub("\\"..result["tagtype"],"}\\"..result["tagtype"]) 90 | tt.tag = tt.tag.."\\"..result["tagtype"].."0" 91 | for p,q in tt.tag:gmatch("([^}]*)}\\"..result["tagtype"].."([%d%.%-]+)") do 92 | count = count + 1 93 | if (count == result["index"]) then 94 | table.insert(tag_table,{index=i,value=q,change=""}) 95 | goto get_information 96 | end 97 | end 98 | count = count - 1 99 | end 100 | :: get_information :: 101 | end 102 | end 103 | 104 | --analyze the table 105 | local sum_diff = 0 106 | for i=1,N-1 do 107 | sum_diff = sum_diff + math.abs(tag_table[i+1].value - tag_table[i].value) 108 | end 109 | local average_diff = sum_diff / (N-1) + 0.001 110 | 111 | --upper threshold 112 | local tag_table_upper={}--a copy of tag_table 113 | for i=2,N-1 do 114 | local diff_l = math.abs(tag_table[i].value - tag_table[i-1].value) 115 | local diff_r = math.abs(tag_table[i].value - tag_table[i+1].value) 116 | if (diff_l/average_diff>=result["upper_threshold"] and diff_r/average_diff>=result["upper_threshold"]) then 117 | local tag = tag_table[i] 118 | tag.value = (tag_table[i-1].value + tag_table[i+1].value) / 2 119 | tag.change = "U" 120 | table.insert(tag_table_upper,tag) 121 | end 122 | end 123 | for si,upper in ipairs(tag_table_upper) do 124 | local tag = tag_table[upper.index] 125 | tag.value = upper.value 126 | tag.change = upper.change 127 | tag_table[upper.index] = tag 128 | end 129 | --get the average_diff again 130 | sum_diff = 0 131 | for i=1,N-1 do 132 | sum_diff = sum_diff + math.abs(tag_table[i+1].value - tag_table[i].value) 133 | end 134 | average_diff = sum_diff / (N-1) + 0.001 135 | --lower threshold 136 | local tag_table_lower={}--a copy of tag_table 137 | for i=2,N-1 do 138 | local diff_l = math.abs(tag_table[i].value - tag_table[i-1].value) 139 | local diff_r = math.abs(tag_table[i].value - tag_table[i+1].value) 140 | local diff_c = tag_table[i].value - (tag_table[i-1].value + tag_table[i+1].value)/2 141 | if (diff_l/average_diff>=result["lower_threshold"] or diff_r/average_diff>=result["lower_threshold"]) then 142 | local tag = tag_table[i] 143 | tag.value = tag_table[i].value - diff_c * result["force"] 144 | tag.change = tag.change.."L" 145 | table.insert(tag_table_lower,tag) 146 | end 147 | end 148 | for si,lower in ipairs(tag_table_lower) do 149 | local tag = tag_table[lower.index] 150 | tag.value = lower.value 151 | tag.change = lower.change 152 | tag_table[lower.index] = tag 153 | end 154 | --analyze tag_table end 155 | 156 | --write back 157 | if (result["tagtype"]=="posx" or result["tagtype"]=="posy") then 158 | local i = 0 159 | for si,li in ipairs(selected) do 160 | i = i + 1 161 | local line = subtitle[li] 162 | local ltext = line.text 163 | if (result["tagtype"]=="posx") then 164 | ltext = ltext:gsub("\\pos%([%d%.%-]+",string.format("\\pos(%.3f",tag_table[i].value)) 165 | else 166 | ltext = ltext:gsub("(\\pos%([%d%.%-]+,)[%d%.%-]+",function(a) return string.format("%s%.3f",a,tag_table[i].value) end) 167 | end 168 | line.text = ltext 169 | line.actor = tag_table[i].change 170 | subtitle[li] = line 171 | end 172 | elseif (result["tagtype"]=="fscx" or result["tagtype"]=="fscy" or result["tagtype"]=="frz") then 173 | local i = 0 174 | for si,li in ipairs(selected) do 175 | i = i + 1 176 | local line = subtitle[li] 177 | local ltext = line.text 178 | 179 | local tt_table={} 180 | for tg,tx in ltext:gmatch("({[^}]*})([^{]*)") do 181 | table.insert(tt_table,{tag=tg,text=tx}) 182 | end 183 | 184 | local rebuild = "" 185 | local count = 0 186 | for _,tt in ipairs(tt_table) do 187 | if (tt.tag:match("\\"..result["tagtype"])==nil) then 188 | rebuild = rebuild..tt.tag..tt.text 189 | else 190 | tt.tag = tt.tag:gsub("\\"..result["tagtype"],"}\\"..result["tagtype"]) 191 | tt.tag = tt.tag.."\\"..result["tagtype"].."0" 192 | local rebuild_tag = "" 193 | 194 | for p,q in tt.tag:gmatch("([^}]*)}\\"..result["tagtype"].."([%d%.%-]+)") do 195 | count = count + 1 196 | if (count == result["index"]) then 197 | rebuild_tag = string.format("%s%s\\%s%.2f",rebuild_tag,p,result["tagtype"],tag_table[i].value) 198 | else 199 | rebuild_tag = rebuild_tag..p.."\\"..result["tagtype"]..q 200 | end 201 | end 202 | rebuild_tag = rebuild_tag:gsub("\\"..result["tagtype"].."[%d%.%-]+$","}") 203 | rebuild = rebuild..rebuild_tag..tt.text 204 | count = count - 1 205 | end 206 | end 207 | ltext = rebuild 208 | line.text = ltext 209 | line.actor = tag_table[i].change 210 | subtitle[li] = line 211 | end 212 | end 213 | end 214 | aegisub.set_undo_point(script_name) 215 | return selected 216 | end 217 | 218 | function config_read () 219 | local path = "" 220 | local config_table = {tag="fscx",index=1,U=500,L=180,F=30,remember=false,keep_U=false,keep_L=false} 221 | for p in lfs.dir("C:\\Users") do 222 | if (p~="Public" and p:match("Default")==nil) then 223 | path = "C:\\Users\\"..p.."\\AppData\\Roaming\\Aegisub\\C Smooth.txt" 224 | end 225 | end 226 | if io.open(path,"r") then 227 | file = io.open(path,"r") 228 | io.input(file) 229 | for line in io.lines(path) do 230 | if line:match("tag") then config_table.tag=line:match(":(.*)") 231 | elseif line:match("index") then config_table.index=line:match(":(.*)") 232 | elseif line:match("upper_threshold") then config_table.U=line:match(":(.*)") 233 | elseif line:match("lower_threshold") then config_table.L=line:match(":(.*)") 234 | elseif line:match("force") then config_table.F=line:match(":(.*)") 235 | elseif line:match("keep_U") then config_table.keep_U=line:match(":(.*)")=="true" and true or false 236 | elseif line:match("keep_L") then config_table.keep_L=line:match(":(.*)")=="true" and true or false 237 | elseif line:match("remember") then config_table.remember=line:match(":(.*)")=="true" and true or false 238 | end 239 | end 240 | io.close(file) 241 | end 242 | return config_table 243 | end 244 | 245 | function config_write(result) 246 | local path = "" 247 | for p in lfs.dir("C:\\Users") do 248 | if (p~="Public" and p:match("Default")==nil) then 249 | path = "C:\\Users\\"..p.."\\AppData\\Roaming\\Aegisub\\C Smooth.txt" 250 | end 251 | end 252 | file = io.open(path,"w+") 253 | io.output(file) 254 | io.write("tag:"..result.tagtype.."\n") 255 | io.write("index:"..result.index.."\n") 256 | io.write("upper_threshold:"..result.upper_threshold.."\n") 257 | io.write("lower_threshold:"..result.lower_threshold.."\n") 258 | io.write("force:"..result.force.."\n") 259 | keep_U = result.keep_U and "true" or "false" 260 | keep_L = result.keep_L and "true" or "false" 261 | remember = result.remember and "true" or "false" 262 | io.write("keep_U:"..keep_U.."\n") 263 | io.write("keep_L:"..keep_L.."\n") 264 | io.write("remember:"..remember) 265 | io.close(file) 266 | end 267 | 268 | --This optional function lets you prevent the user from running the macro on bad input 269 | function macro_validation(subtitle, selected, active) 270 | --Check if the user has selected valid lines 271 | --If so, return true. Otherwise, return false 272 | return true 273 | end 274 | 275 | --This is what puts your automation in Aegisub's automation list 276 | aegisub.register_macro(script_name,script_description,main,macro_validation) 277 | 278 | -------------------------------------------------------------------------------- /abandoned/Effect life game.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | ]] 5 | 6 | --Script properties 7 | script_name="Effect: life game" 8 | script_description="life game v1.0" 9 | script_author="chaaaaang" 10 | script_version="1.0" 11 | 12 | local Yutils = require('Yutils') 13 | include('karaskel.lua') 14 | 15 | function main(subtitle, selected) 16 | local meta,styles=karaskel.collect_head(subtitle,false) 17 | local xres, yres, ar, artype = aegisub.video_size() 18 | 19 | -- the pixel of every block 20 | local block = 5 21 | local data = "" 22 | local x_min,x_max,y_min,y_max = 0,0,0,0 23 | 24 | for si,li in ipairs(selected) do 25 | 26 | local line=subtitle[li] 27 | karaskel.preproc_line(subtitle,meta,styles,line) 28 | 29 | if si==1 then 30 | -- line 31 | local ltxtstripped = line.text_stripped 32 | local ltext = line.text:match("^{") and line.text or "{}"..line.text 33 | -- tag 34 | local tag = ltext:match("^{[^}]*}") 35 | local tag_strip_t = tag:gsub("\\t%([^%)]*%)","") 36 | -- inline style 37 | local font = tag_strip_t:match("\\fn") and tag_strip_t:match("\\fn([^\\}]+)") or line.styleref.fontname 38 | local fontsize = tag_strip_t:match("\\fs%d") and tag_strip_t:match("\\fs([%d%.]+)") or line.styleref.fontsize 39 | local bold = tag_strip_t:match("\\b%d") and num2bool(tag_strip_t:match("\\b(%d)")) or line.styleref.bold 40 | local italic = tag_strip_t:match("\\i%d") and num2bool(tag_strip_t:match("\\i(%d)")) or line.styleref.italic 41 | local underline = tag_strip_t:match("\\u%d") and num2bool(tag_strip_t:match("\\u(%d)")) or line.styleref.underline 42 | local strikeout = tag_strip_t:match("\\s%d") and num2bool(tag_strip_t:match("\\s(%d)")) or line.styleref.strikeout 43 | local scale_x = tag_strip_t:match("\\fscx") and tag_strip_t:match("\\fscx([%d%.]+)") or line.styleref.scale_x 44 | local scale_y = tag_strip_t:match("\\fscy") and tag_strip_t:match("\\fscy([%d%.]+)") or line.styleref.scale_y 45 | local spacing = tag_strip_t:match("\\fsp") and tag_strip_t:match("\\fsp([%d%.%-]+)") or line.styleref.spacing 46 | 47 | local font_handle = Yutils.decode.create_font(font,bold,italic,underline,strikeout,fontsize,scale_x/100,scale_y/100,spacing) 48 | local shape = font_handle.text_to_shape(ltxtstripped) 49 | local pixels = Yutils.shape.to_pixels(shape) 50 | 51 | local posx,posy,top,left,bottom,right,center,middle = position(ltext,line,xres,yres) 52 | x_min,x_max,y_min,y_max = center,center,middle,middle 53 | 54 | for i=math.floor(left),math.floor(right),block do 55 | for j=math.floor(top),math.floor(bottom),block do 56 | if pt_in_shape(i-left,j-top,pixels)==true then 57 | data = data..string.format("m %d %d l %d %d %d %d %d %d ",i,j,i+block,j,i+block,j+block,i,j+block) 58 | x_min,x_max,y_min,y_max = math.min(x_min,i),math.max(x_max,i),math.min(y_min,j),math.max(y_max,j) 59 | end 60 | end 61 | end 62 | else 63 | local data_temp = "" 64 | local data_table = {} 65 | 66 | for x,y in data:gmatch("m ([%d%-]+) ([%d%-]+)") do 67 | local c1,c2,c3,c4,c6,c7,c8,c9 = 0,0,0,0,0,0,0,0 68 | x,y = tonumber(x),tonumber(y) 69 | for sj,lj in ipairs(data_table) do 70 | if x-block==lj.x and y-block==lj.y then 71 | data_table[sj].c = lj.c + 1 72 | c7 = 1 73 | elseif x==lj.x and y-block==lj.y then 74 | data_table[sj].c = lj.c + 1 75 | c8 = 1 76 | elseif x+block==lj.x and y-block==lj.y then 77 | data_table[sj].c = lj.c + 1 78 | c9 = 1 79 | elseif x-block==lj.x and y==lj.y then 80 | data_table[sj].c = lj.c + 1 81 | c4 = 1 82 | elseif x+block==lj.x and y==lj.y then 83 | data_table[sj].c = lj.c + 1 84 | c6 = 1 85 | elseif x-block==lj.x and y+block==lj.y then 86 | data_table[sj].c = lj.c + 1 87 | c1 = 1 88 | elseif x==lj.x and y+block==lj.y then 89 | data_table[sj].c = lj.c + 1 90 | c2 = 1 91 | elseif x+block==lj.x and y+block==lj.y then 92 | data_table[sj].c = lj.c + 1 93 | c3 = 1 94 | end 95 | end 96 | if c1==0 then table.insert(data_table,{x=x-block,y=y+block,c=1}) end 97 | if c2==0 then table.insert(data_table,{x=x,y=y+block,c=1}) end 98 | if c3==0 then table.insert(data_table,{x=x+block,y=y+block,c=1}) end 99 | if c4==0 then table.insert(data_table,{x=x-block,y=y,c=1}) end 100 | if c6==0 then table.insert(data_table,{x=x+block,y=y,c=1}) end 101 | if c7==0 then table.insert(data_table,{x=x-block,y=y-block,c=1}) end 102 | if c8==0 then table.insert(data_table,{x=x,y=y-block,c=1}) end 103 | if c9==0 then table.insert(data_table,{x=x+block,y=y-block,c=1}) end 104 | end 105 | 106 | for sj,lj in ipairs(data_table) do 107 | if lj.c==3 or (lj.c==2 and data:match(string.format("m %d %d",lj.x,lj.y))~=nil) then 108 | local i,j = lj.x,lj.y 109 | data_temp = data_temp..string.format("m %d %d l %d %d %d %d %d %d ",i,j,i+block,j,i+block,j+block,i,j+block) 110 | x_min,x_max,y_min,y_max = math.min(x_min,i),math.max(x_max,i),math.min(y_min,j),math.max(y_max,j) 111 | end 112 | end 113 | 114 | data = data_temp 115 | end 116 | line.text = "{\\an7\\p1\\pos(0,0)\\fsc100\\bord0\\shad0}"..data 117 | subtitle[li]=line 118 | aegisub.progress.set(si/#selected*100) 119 | end 120 | 121 | aegisub.set_undo_point(script_name) 122 | return selected 123 | end 124 | 125 | function num2bool(a) 126 | if tonumber(a)~=0 then 127 | return true 128 | else 129 | return false 130 | end 131 | end 132 | 133 | function position(ltext,line,xres,yres) 134 | local x,y,top,left,bottom,right,center,middle = 0,0,0,0,0,0,0,0 135 | 136 | local ratiox,ratioy = 1,1 137 | if (ltext:match("\\fs%d")~=nil) then 138 | ratiox = tonumber(ltext:match("\\fs([%d%.]+)")) / line.styleref.fontsize 139 | ratioy = tonumber(ltext:match("\\fs([%d%.]+)")) / line.styleref.fontsize 140 | end 141 | if (ltext:match("\\fscx")~=nil) then 142 | ratiox = ratiox * tonumber(ltext:match("\\fscx([%d%.]+)")) / line.styleref.scale_x 143 | end 144 | if (ltext:match("\\fscy")~=nil) then 145 | ratioy = ratioy * tonumber(ltext:match("\\fscy([%d%.]+)")) / line.styleref.scale_y 146 | end 147 | local width = line.width * ratiox 148 | local height = line.height * ratioy 149 | local an = ltext:match("\\an%d") and tonumber(ltext:match("\\an(%d)")) or line.styleref.align 150 | if (an == 1) then 151 | if (ltext:match("\\pos")~=nil) then 152 | left = tonumber(ltext:match("\\pos%(([^,]+)")) 153 | bottom = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 154 | else 155 | left = line.styleref.margin_l 156 | bottom = yres-line.styleref.margin_b 157 | end 158 | x,y = left,bottom 159 | right = left + width 160 | top = bottom - height 161 | elseif (an == 2) then 162 | if (ltext:match("\\pos")~=nil) then 163 | center = tonumber(ltext:match("\\pos%(([^,]+)")) 164 | bottom = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 165 | else 166 | center = xres/2 167 | bottom = yres-line.styleref.margin_b 168 | end 169 | x,y = center,bottom 170 | left = center - width / 2 171 | right = center + width / 2 172 | top = bottom - height 173 | elseif (an == 3) then 174 | if (ltext:match("\\pos")~=nil) then 175 | right = tonumber(ltext:match("\\pos%(([^,]+)")) 176 | bottom = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 177 | else 178 | right = xres-line.styleref.margin_r 179 | bottom = yres-line.styleref.margin_b 180 | end 181 | x,y = right,bottom 182 | left = right - width 183 | top = bottom - height 184 | elseif (an == 4) then 185 | if (ltext:match("\\pos")~=nil) then 186 | left = tonumber(ltext:match("\\pos%(([^,]+)")) 187 | middle = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 188 | else 189 | left = line.styleref.margin_l 190 | middle = yres/2 191 | end 192 | x,y = left,middle 193 | right = left + width 194 | top = middle - height / 2 195 | bottom = middle + height / 2 196 | elseif (an == 5) then 197 | if (ltext:match("\\pos")~=nil) then 198 | center = tonumber(ltext:match("\\pos%(([^,]+)")) 199 | middle = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 200 | else 201 | center = xres/2 202 | middle = yres/2 203 | end 204 | x,y = center,middle 205 | left = center - width / 2 206 | right = center + width / 2 207 | top = middle - height / 2 208 | bottom = middle + height / 2 209 | elseif (an == 6) then 210 | if (ltext:match("\\pos")~=nil) then 211 | right = tonumber(ltext:match("\\pos%(([^,]+)")) 212 | middle = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 213 | else 214 | right = xres-line.styleref.margin_r 215 | middle = yres/2 216 | end 217 | x,y = right,middle 218 | left = right - width 219 | top = middle - height / 2 220 | bottom = middle + height / 2 221 | elseif (an == 7) then 222 | if (ltext:match("\\pos")~=nil) then 223 | left = tonumber(ltext:match("\\pos%(([^,]+)")) 224 | top = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 225 | else 226 | left = line.styleref.margin_l 227 | top = line.styleref.margin_t 228 | end 229 | x,y = left,top 230 | right = left + width 231 | bottom = top + height 232 | elseif (an == 8) then 233 | if (ltext:match("\\pos")~=nil) then 234 | center = tonumber(ltext:match("\\pos%(([^,]+)")) 235 | top = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 236 | else 237 | center = xres/2 238 | top = line.styleref.margin_t 239 | end 240 | x,y = center,top 241 | left = center - width / 2 242 | right = center + width / 2 243 | bottom = top + height 244 | elseif (an == 9) then 245 | if (ltext:match("\\pos")~=nil) then 246 | right = tonumber(ltext:match("\\pos%(([^,]+)")) 247 | top = tonumber(ltext:match("\\pos%([^,]+,([^%)]+)")) 248 | else 249 | right = xres-line.styleref.margin_r 250 | top = line.styleref.margin_t 251 | end 252 | x,y = right,top 253 | left = right - width 254 | bottom = top + height 255 | else 256 | end 257 | center,middle = (left+right)/2, (top+bottom)/2 258 | return x,y,top,left,bottom,right,center,middle 259 | end 260 | 261 | function pt_in_shape(x,y,pixels) 262 | for j=1,#pixels do 263 | if (math.abs(x-pixels[j].x)<=0.5 and math.abs(y-pixels[j].y)<=0.5) then 264 | return true 265 | end 266 | end 267 | return false 268 | end 269 | 270 | --Register macro (no validation function required) 271 | aegisub.register_macro(script_name,script_description,main) 272 | 273 | -------------------------------------------------------------------------------- /abandoned/ForceTwoWindow.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import argparse 3 | 4 | parser = argparse.ArgumentParser(description="Force two window.") 5 | parser.add_argument("-i","--input", type=str, nargs="+") 6 | parser.add_argument("-o","--output", type=str, nargs="+") 7 | parser.add_argument("-m","--mode", type=int, default=0, help="0: crop and convert, 1: merge, 2: convert only") 8 | parser.add_argument("-c","--crop", type=int, nargs="+") 9 | parser.add_argument("-u","--unite",type=int, nargs=6, help="x,y,posx1,posy1,posx2,posy2") 10 | args = parser.parse_args() 11 | 12 | inputpath, outputpath = args.input, args.output 13 | 14 | if args.mode==2: 15 | im = Image.open(inputpath[0]) 16 | im = im.convert("P", colors=256) 17 | im.save(outputpath[0]) 18 | elif args.mode==0: 19 | im = Image.open(inputpath[0]) 20 | crops = args.crop 21 | box1 = (crops[0], crops[1], crops[2], crops[3]) 22 | im1 = im.crop(box1) 23 | im1 = im1.convert("P", colors=256) 24 | im1.save(outputpath[0]) 25 | if len(crops)==8: 26 | box2 = (crops[4], crops[5], crops[6], crops[7]) 27 | im2 = im.crop(box2) 28 | im2 = im2.convert("P", colors=256) 29 | im2.save(outputpath[1]) 30 | else: # args.mode==1 31 | im1, im2 = Image.open(inputpath[0]), Image.open(inputpath[1]) 32 | u = args.unite 33 | box1 = (u[2], u[3]) 34 | box2 = (u[4], u[5]) 35 | canvas = Image.new("RGBA", size=(u[0], u[1]), color=(0,0,0,0)) 36 | canvas.paste(im1, box=box1) 37 | canvas.paste(im2, box=box2) 38 | crops = args.crop 39 | box1 = (crops[0], crops[1], crops[2], crops[3]) 40 | im1 = canvas.crop(box1) 41 | im1 = im1.convert("P", colors=256) 42 | im1.save(outputpath[0]) 43 | if len(crops)==8: 44 | box2 = (crops[4], crops[5], crops[6], crops[7]) 45 | im2 = canvas.crop(box2) 46 | im2 = im2.convert("P", colors=256) 47 | im2.save(outputpath[1]) 48 | 49 | 50 | -------------------------------------------------------------------------------- /cheatsheet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/0dd6db390bef59a86ff0728da469653c2bd69ac1/cheatsheet.xlsx -------------------------------------------------------------------------------- /docs/function smooth.cdf: -------------------------------------------------------------------------------- 1 | (* Content-type: application/vnd.wolfram.cdf.text *) 2 | 3 | (*** Wolfram CDF File ***) 4 | (* http://www.wolfram.com/cdf *) 5 | 6 | (* CreatedBy='Mathematica 11.3' *) 7 | 8 | (***************************************************************************) 9 | (* *) 10 | (* *) 11 | (* Under the Wolfram FreeCDF terms of use, this file and its content are *) 12 | (* bound by the Creative Commons BY-SA Attribution-ShareAlike license. *) 13 | (* *) 14 | (* For additional information concerning CDF licensing, see: *) 15 | (* *) 16 | (* www.wolfram.com/cdf/adopting-cdf/licensing-options.html *) 17 | (* *) 18 | (* *) 19 | (***************************************************************************) 20 | 21 | (*CacheID: 234*) 22 | (* Internal cache information: 23 | NotebookFileLineBreakTest 24 | NotebookFileLineBreakTest 25 | NotebookDataPosition[ 1088, 20] 26 | NotebookDataLength[ 4366, 112] 27 | NotebookOptionsPosition[ 4857, 108] 28 | NotebookOutlinePosition[ 5214, 124] 29 | CellTagsIndexPosition[ 5171, 121] 30 | WindowFrame->Normal*) 31 | 32 | (* Beginning of Notebook Content *) 33 | Notebook[{ 34 | 35 | Cell[CellGroupData[{ 36 | Cell[BoxData[ 37 | RowBox[{"Manipulate", "[", 38 | RowBox[{ 39 | RowBox[{"Plot", "[", 40 | RowBox[{ 41 | RowBox[{ 42 | RowBox[{"(", 43 | RowBox[{ 44 | RowBox[{"(", 45 | RowBox[{"1", "-", 46 | RowBox[{"Cos", "[", 47 | RowBox[{"2", " ", "Pi", " ", 48 | RowBox[{"x", "^", "transverse"}]}], "]"}]}], ")"}], "/", "2"}], 49 | ")"}], "^", "accel"}], ",", 50 | RowBox[{"{", 51 | RowBox[{"x", ",", "0", ",", "1"}], "}"}], ",", 52 | RowBox[{"PlotRange", "\[Rule]", "Full"}]}], "]"}], ",", 53 | RowBox[{"{", 54 | RowBox[{"accel", ",", "0.2", ",", "5"}], "}"}], ",", 55 | RowBox[{"{", 56 | RowBox[{"transverse", ",", "0.2", ",", "5"}], "}"}]}], "]"}]], "Input", 57 | CellChangeTimes->{{3.8222056208721905`*^9, 3.822205624809825*^9}, { 58 | 3.822205694257448*^9, 3.8222058266299243`*^9}, {3.8222058621542797`*^9, 59 | 3.822205913262742*^9}, {3.8222061005051966`*^9, 3.82220611120822*^9}, { 60 | 3.82220617598127*^9, 3.822206178749225*^9}}, 61 | CellLabel->"In[4]:=",ExpressionUUID->"4d1d1848-7c28-425a-be44-9659f20bc0f6"], 62 | 63 | Cell[BoxData[ 64 | TagBox[ 65 | StyleBox[ 66 | DynamicModuleBox[{$CellContext`accel$$ = 67 | 0.9999999999999989, $CellContext`transverse$$ = 1.0000000000000022`, 68 | Typeset`show$$ = True, Typeset`bookmarkList$$ = {}, 69 | Typeset`bookmarkMode$$ = "Menu", Typeset`animator$$, Typeset`animvar$$ = 70 | 1, Typeset`name$$ = "\"\:65e0\:6807\:9898\"", Typeset`specs$$ = {{ 71 | Hold[$CellContext`accel$$], 0.2, 5}, { 72 | Hold[$CellContext`transverse$$], 0.2, 5}}, Typeset`size$$ = { 73 | 360., {108., 113.}}, Typeset`update$$ = 0, Typeset`initDone$$, 74 | Typeset`skipInitDone$$ = True, $CellContext`accel$23137$$ = 75 | 0, $CellContext`transverse$23138$$ = 0}, 76 | DynamicBox[Manipulate`ManipulateBoxes[ 77 | 1, StandardForm, 78 | "Variables" :> {$CellContext`accel$$ = 0.2, $CellContext`transverse$$ = 79 | 0.2}, "ControllerVariables" :> { 80 | Hold[$CellContext`accel$$, $CellContext`accel$23137$$, 0], 81 | Hold[$CellContext`transverse$$, $CellContext`transverse$23138$$, 0]}, 82 | "OtherVariables" :> { 83 | Typeset`show$$, Typeset`bookmarkList$$, Typeset`bookmarkMode$$, 84 | Typeset`animator$$, Typeset`animvar$$, Typeset`name$$, 85 | Typeset`specs$$, Typeset`size$$, Typeset`update$$, Typeset`initDone$$, 86 | Typeset`skipInitDone$$}, "Body" :> 87 | Plot[((1 - Cos[(2 Pi) $CellContext`x^$CellContext`transverse$$])/ 88 | 2)^$CellContext`accel$$, {$CellContext`x, 0, 1}, PlotRange -> Full], 89 | "Specifications" :> {{$CellContext`accel$$, 0.2, 90 | 5}, {$CellContext`transverse$$, 0.2, 5}}, "Options" :> {}, 91 | "DefaultOptions" :> {}], 92 | ImageSizeCache->{411., {169., 175.}}, 93 | SingleEvaluation->True], 94 | Deinitialization:>None, 95 | DynamicModuleValues:>{}, 96 | SynchronousInitialization->True, 97 | UndoTrackedVariables:>{Typeset`show$$, Typeset`bookmarkMode$$}, 98 | UnsavedVariables:>{Typeset`initDone$$}, 99 | UntrackedVariables:>{Typeset`size$$}], "Manipulate", 100 | Deployed->True, 101 | StripOnInput->False], 102 | Manipulate`InterpretManipulate[1]]], "Output", 103 | CellChangeTimes->{{3.8222059072419353`*^9, 3.822205947044544*^9}, { 104 | 3.822206116755273*^9, 3.8222061463927116`*^9}, 3.8222061812135763`*^9, { 105 | 3.8222063024054003`*^9, 3.822206307810097*^9}, 3.8222065071056924`*^9}, 106 | CellLabel->"Out[4]=",ExpressionUUID->"43484219-9e42-46c1-82bb-644d35be7436"] 107 | }, Open ]] 108 | }, 109 | WindowSize->{759, 553}, 110 | WindowMargins->{{Automatic, 199}, {Automatic, 52}}, 111 | FrontEndVersion->"11.3 for Microsoft Windows (64-bit) (2018\:5e743\:670828\ 112 | \:65e5)", 113 | StyleDefinitions->"Default.nb" 114 | ] 115 | (* End of Notebook Content *) 116 | 117 | (* Internal cache information *) 118 | (*CellTagsOutline 119 | CellTagsIndex->{} 120 | *) 121 | (*CellTagsIndex 122 | CellTagsIndex->{} 123 | *) 124 | (*NotebookFileOutline 125 | Notebook[{ 126 | Cell[CellGroupData[{ 127 | Cell[1510, 35, 1022, 25, 66, "Input",ExpressionUUID->"4d1d1848-7c28-425a-be44-9659f20bc0f6"], 128 | Cell[2535, 62, 2306, 43, 363, "Output",ExpressionUUID->"43484219-9e42-46c1-82bb-644d35be7436"] 129 | }, Open ]] 130 | } 131 | ] 132 | *) 133 | 134 | (* NotebookSignature #xpWEFOSb3USlDwFOJ3ZVQYP *) 135 | -------------------------------------------------------------------------------- /index.json: -------------------------------------------------------------------------------- 1 | { 2 | "C Change SUB resolution to match video PATCH": 3 | { 4 | "version": "1.2", 5 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Change%20SUB%20resolution%20to%20match%20video%20PATCH.lua" 6 | }, 7 | 8 | "C Effect": 9 | { 10 | "version": "1.6", 11 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Effect.lua" 12 | }, 13 | 14 | "C Fast Tools": 15 | { 16 | "version": "1.2.1", 17 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Fast%20Tools.lua" 18 | }, 19 | 20 | "C Font Resize": 21 | { 22 | "version": "1.3", 23 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Font%20Resize.lua" 24 | }, 25 | 26 | "C Gradient": 27 | { 28 | "version": "2.2", 29 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Gradient.lua" 30 | }, 31 | 32 | "C Jump": 33 | { 34 | "version": "1.0", 35 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Jump.lua" 36 | }, 37 | 38 | "C Merge Bilingual SUBS": 39 | { 40 | "version": "1.2", 41 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Merge%20Bilingual%20SUBS.lua" 42 | }, 43 | 44 | "C Replace Plus": 45 | { 46 | "version": "1.0", 47 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Replace%20Plus.lua" 48 | }, 49 | 50 | "C Translation": 51 | { 52 | "version": "3.2.1", 53 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Translation.lua" 54 | }, 55 | 56 | "C Utilities": 57 | { 58 | "version": "1.7.5", 59 | "url": "https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/main/src/C%20Utilities.lua" 60 | } 61 | } -------------------------------------------------------------------------------- /lib/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/0dd6db390bef59a86ff0728da469653c2bd69ac1/lib/0.png -------------------------------------------------------------------------------- /lib/00000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhang-changwei/Automation-scripts-for-Aegisub/0dd6db390bef59a86ff0728da469653c2bd69ac1/lib/00000000.png -------------------------------------------------------------------------------- /lib/ColorCalibration.py: -------------------------------------------------------------------------------- 1 | # version 1.1 2 | 3 | import tkinter as tk 4 | import tkinter.filedialog as filedialog 5 | import tkinter.messagebox as messagebox 6 | import tkinter.ttk as ttk 7 | from PIL import Image 8 | import numpy as np 9 | import colour 10 | import os 11 | 12 | class App: 13 | 14 | def __init__(self): 15 | self.root = tk.Tk() 16 | self.root.title('Color Calibration') 17 | 18 | self.files = () 19 | self.sdr = '' 20 | self.hdr = '' 21 | self.param = tk.IntVar(value=30) 22 | 23 | grid1 = ttk.Labelframe(self.root, text='Convert') 24 | grid1.pack(side='top', fill='x', padx=10, pady=(10, 5)) 25 | grid2 = ttk.Labelframe(self.root, text='Compare') 26 | grid2.pack(side='top', fill='x', padx=10, pady=(5, 5)) 27 | grid3 = ttk.Labelframe(self.root, text='Parameter') 28 | grid3.pack(side='top', fill='x', padx=10, pady=(5, 10)) 29 | 30 | ttk.Button(grid1, text='Select', width=15, command=self.selectFiles) \ 31 | .pack(side='left', fill='y', padx=4, pady=4) 32 | ttk.Button(grid1, text='SDR -> HDR', width=15, command=lambda x='S2H': self.convert(x)) \ 33 | .pack(side='left', fill='y', padx=4, pady=4) 34 | ttk.Button(grid1, text='HDR -> SDR', width=15, command=lambda x='H2S': self.convert(x)) \ 35 | .pack(side='left', fill='y', padx=4, pady=4) 36 | 37 | ttk.Button(grid2, text='SDR', width=15, command=lambda x='SDR': self.selectFile(x)) \ 38 | .pack(side='left', fill='y', padx=4, pady=4) 39 | ttk.Button(grid2, text='HDR', width=15, command=lambda x='HDR': self.selectFile(x)) \ 40 | .pack(side='left', fill='y', padx=4, pady=4) 41 | ttk.Button(grid2, text='Run', width=15, command=self.compare) \ 42 | .pack(side='left', fill='y', padx=4, pady=4) 43 | 44 | ttk.Entry(grid3, textvariable=self.param).pack(fill='both', padx=4, pady=4) 45 | 46 | self.root.mainloop() 47 | 48 | def selectFiles(self): 49 | path = filedialog.askopenfilenames(title='Select', filetypes=[('PNG File', ['.png', '.PNG'])]) 50 | if path: 51 | self.files = path 52 | print(f'Select: {path}') 53 | 54 | def selectFile(self, x): 55 | path = filedialog.askopenfilename(filetypes=[('PNG File', ['.png', '.PNG', '.*'])]) 56 | if path: 57 | if x == 'SDR': 58 | self.sdr = path 59 | print(f'SDR: {path}') 60 | elif x == 'HDR': 61 | self.hdr = path 62 | print(f'HDR: {path}') 63 | 64 | def convert(self, x:str): 65 | if self.files: 66 | for file in self.files: 67 | try: 68 | im = Image.open(file) 69 | arr = np.asarray(im) 70 | channel = np.size(arr, axis=-1) 71 | if channel == 4: 72 | rgb = arr[..., :3] 73 | a = arr[..., -1:] 74 | else: 75 | rgb = arr 76 | # main function 77 | if x == 'S2H': 78 | rgb = self.sdr2hdr(rgb).astype(np.uint8) 79 | else: 80 | rgb = self.hdr2sdr(rgb).astype(np.uint8) 81 | if channel == 4: 82 | arr = np.dstack((rgb, a)) 83 | im = Image.fromarray(arr, mode='RGBA') 84 | else: 85 | arr = rgb 86 | im = Image.fromarray(arr, mode='RGB') 87 | # save 88 | head, tail = os.path.split(file) 89 | if x == 'S2H': 90 | im.save(os.path.join(head, 'HDR_' + tail)) 91 | else: 92 | im.save(os.path.join(head, 'SDR_' + tail)) 93 | except: 94 | print(f'An error occured when converting "{file}".') 95 | messagebox.showinfo(message='Convertion finished.') 96 | 97 | def compare(self): 98 | if self.sdr and self.hdr: 99 | try: 100 | sdr = Image.open(self.sdr) 101 | hdr = Image.open(self.hdr) 102 | sdrrgb = np.asarray(sdr)[..., :3] 103 | hdrrgb = np.asarray(hdr)[..., :3] 104 | sdrrgb = self.sdr2hdr(sdrrgb) 105 | err = np.abs(sdrrgb - hdrrgb) 106 | im = Image.fromarray(err.astype(np.uint8)) 107 | im.show() 108 | except: 109 | print(f'An error occured when comparing "{self.sdr}" with "{self.hdr}".') 110 | 111 | def sdr2hdr(self, rgb:np.ndarray): 112 | rgb = colour.models.eotf_sRGB(rgb / 255) 113 | rgb = colour.models.RGB_to_RGB(rgb, 114 | colour.models.RGB_COLOURSPACE_sRGB, 115 | colour.models.RGB_COLOURSPACE_BT2020, 116 | chromatic_adaptation_transform='XYZ Scaling') 117 | rgb = colour.models.oetf_PQ_BT2100(rgb / self.param.get()) 118 | rgb = colour.models.RGB_to_YCbCr(rgb, colour.WEIGHTS_YCBCR['ITU-R BT.2020']) 119 | rgb = colour.models.YCbCr_to_RGB(rgb, colour.WEIGHTS_YCBCR['ITU-R BT.709']) 120 | rgb *= 255 121 | return rgb 122 | 123 | def hdr2sdr(self, rgb:np.ndarray): 124 | rgb = colour.models.RGB_to_YCbCr(rgb / 255, colour.WEIGHTS_YCBCR['ITU-R BT.709']) 125 | rgb = colour.models.YCbCr_to_RGB(rgb, colour.WEIGHTS_YCBCR['ITU-R BT.2020']) 126 | np.putmask(rgb, rgb>1, 1) 127 | np.putmask(rgb, rgb<0, 0) 128 | rgb = colour.models.oetf_inverse_PQ_BT2100(rgb) 129 | rgb *= self.param.get() 130 | np.putmask(rgb, rgb>1, 1) 131 | np.putmask(rgb, rgb<0, 0) 132 | rgb = colour.models.RGB_to_RGB(rgb, 133 | colour.models.RGB_COLOURSPACE_BT2020, 134 | colour.models.RGB_COLOURSPACE_sRGB, 135 | chromatic_adaptation_transform='XYZ Scaling') 136 | np.putmask(rgb, rgb>1, 1) 137 | np.putmask(rgb, rgb<0, 0) 138 | rgb = colour.models.eotf_inverse_sRGB(rgb) 139 | rgb *= 255 140 | return rgb 141 | 142 | if __name__ == '__main__': 143 | App() -------------------------------------------------------------------------------- /lib/ForceTwoWindow2.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageDraw 2 | import re 3 | # version 1.5.5 4 | # parser.add_argument("-i","--input", type=str, nargs="+") 5 | # parser.add_argument("-o","--output", type=str, nargs="+") 6 | # parser.add_argument("-m","--mode", type=int, default=0, help="0: crop and convert, 1: merge, 2: convert only") 7 | # parser.add_argument("-c","--crop", type=int, nargs="+") 8 | # parser.add_argument("-u","--unite",type=int, nargs=6, help="x,y,posx1,posy1,posx2,posy2") 9 | # I have to use pillow 8.2.0 here, they may change the api of palette, it leads to some wired problem. 10 | 11 | class Palette: 12 | 13 | def __init__(self, im:Image.Image): 14 | imRGBA = im.convert('RGBA') 15 | 16 | pix = im.load() 17 | pixRGBA = imRGBA.load() 18 | 19 | colors = {} 20 | self.leastSignificantEntry = [None, 255] 21 | for w in range(im.width): 22 | for h in range(im.height): 23 | if pix[w, h] not in colors.keys(): 24 | i, c = pix[w, h], pixRGBA[w, h] 25 | colors[i] = c 26 | if c[-1] > 0 and c[-1] < self.leastSignificantEntry[1]: 27 | self.leastSignificantEntry = [i, c[-1]] 28 | 29 | self.palette = [] 30 | self.emptyEntry = [] 31 | for i in range(256): 32 | if i in colors.keys(): 33 | self.palette.extend(colors[i]) 34 | else: 35 | self.emptyEntry.append(i) 36 | self.palette.extend((0, 0, 0, 0)) 37 | 38 | def setColor(self, color=(0, 0, 0, 2)) -> int: 39 | if self.emptyEntry: 40 | ind = self.emptyEntry[0] 41 | self.palette[ind*4: ind*4 + 4] = color 42 | self.emptyEntry.pop(0) 43 | return ind 44 | elif self.leastSignificantEntry[0]: 45 | ind = self.leastSignificantEntry[0] 46 | self.palette[ind*4: ind*4 + 4] = color 47 | self.leastSignificantEntry = [None, 255] 48 | else: 49 | raise ValueError('Not enough entry') 50 | 51 | def addBorder(im:Image.Image, color): 52 | rect = ImageDraw.Draw(im) 53 | rect.rectangle([(0, 0), (im.width-1, im.height-1)], outline=color, width=1) 54 | return im 55 | 56 | def openConfig(fp='ForceTwoWindow2.conf'): 57 | file = open(fp, 'r') 58 | 59 | inputpathList, outputpathList, cropsList, uList, modeList = [],[],[],[],[] 60 | 61 | for node in file.readlines(): 62 | inputpath, outputpath, crops, u = [],[],[],[] 63 | mode = 0 64 | node = node.strip() 65 | if re.match('start cmd', node)!=None: 66 | inputStr = re.search('-i (.+?) -', node).group(1) 67 | inputpath = re.split(' ', inputStr) 68 | outputStr = re.search('-o (.+?) -', node).group(1) 69 | outputpath = re.split(' ', outputStr) 70 | if re.search('-m', node)!=None: 71 | mode = int(re.search('-m (\d)', node).group(1)) 72 | if re.search('-c', node)!=None: 73 | cropStr = re.search('-c (.+?)[-\"]', node).group(1).strip() 74 | crops = re.split(' ', cropStr) 75 | crops = list(map(int, crops)) 76 | if re.search('-u', node)!=None: 77 | uStr = re.search('-u (.+?)[-\"]', node).group(1).strip() 78 | u = re.split(' ', uStr) 79 | u = list(map(int, u)) 80 | # print(inputpath, outputpath, mode, crops) 81 | inputpathList.append(inputpath) 82 | outputpathList.append(outputpath) 83 | cropsList.append(crops) 84 | uList.append(u) 85 | modeList.append(mode) 86 | else: continue 87 | 88 | file.close() 89 | 90 | return inputpathList, outputpathList, cropsList, uList, modeList 91 | 92 | if __name__ == '__main__': 93 | inputpathList, outputpathList, cropsList, uList, modeList = openConfig('ForceTwoWindow2.conf') 94 | 95 | length = len(modeList) 96 | for i, inputpath, outputpath, crops, u, mode in zip(range(length), inputpathList, outputpathList, cropsList, uList, modeList): 97 | if mode==2: 98 | im = Image.open(inputpath[0]) 99 | im = im.convert("P", colors=256) 100 | im.save(outputpath[0]) 101 | elif mode==0: # crop and convert 102 | im = Image.open(inputpath[0]) 103 | box1 = (crops[0], crops[1], crops[2], crops[3]) 104 | im1 = im.crop(box1) 105 | 106 | palette = Palette(im1) 107 | color = palette.setColor() 108 | im1 = addBorder(im1, color) 109 | im1.putpalette(palette.palette, 'RGBA') 110 | 111 | im1.save(outputpath[0]) 112 | if len(crops)==8: 113 | box2 = (crops[4], crops[5], crops[6], crops[7]) 114 | im2 = im.crop(box2) 115 | 116 | palette = Palette(im2) 117 | color = palette.setColor() 118 | im2 = addBorder(im2, color) 119 | im2.putpalette(palette.palette, 'RGBA') 120 | 121 | im2.save(outputpath[1]) 122 | else: # mode==1 merge 123 | im1, im2 = Image.open(inputpath[0]), Image.open(inputpath[1]) 124 | box1 = (u[2], u[3]) 125 | box2 = (u[4], u[5]) 126 | im1 = im1.convert('RGBA') 127 | im2 = im2.convert('RGBA') 128 | canvas = Image.new('RGBA', size=(u[0], u[1]), color=(0, 0, 0, 0)) 129 | canvas.paste(im1, box=box1) 130 | canvas.paste(im2, box=box2) 131 | canvas = canvas.convert('P', colors=256) 132 | 133 | box1 = (crops[0], crops[1], crops[2], crops[3]) 134 | im1 = canvas.crop(box1) 135 | 136 | palette = Palette(im1) 137 | color = palette.setColor() 138 | im1 = addBorder(im1, color) 139 | im1.putpalette(palette.palette, 'RGBA') 140 | 141 | im1.save(outputpath[0]) 142 | if len(crops)==8: 143 | box2 = (crops[4], crops[5], crops[6], crops[7]) 144 | im2 = canvas.crop(box2) 145 | 146 | palette = Palette(im2) 147 | color = palette.setColor() 148 | im2 = addBorder(im2, color) 149 | im2.putpalette(palette.palette, 'RGBA') 150 | 151 | im2.save(outputpath[1]) 152 | print('Processing: {}/{}'.format(i+1, length)) -------------------------------------------------------------------------------- /lib/xmlSimple.lua: -------------------------------------------------------------------------------- 1 | module(..., package.seeall) 2 | 3 | --------------------------------------------------------------------------------- 4 | --------------------------------------------------------------------------------- 5 | -- 6 | -- xml.lua - XML parser for use with the Corona SDK. 7 | -- 8 | -- version: 1.2 9 | -- 10 | -- CHANGELOG: 11 | -- 12 | -- 1.2 - Created new structure for returned table 13 | -- 1.1 - Fixed base directory issue with the loadFile() function. 14 | -- 15 | -- NOTE: This is a modified version of Alexander Makeev's Lua-only XML parser 16 | -- found here: http://lua-users.org/wiki/LuaXml 17 | -- 18 | --------------------------------------------------------------------------------- 19 | --------------------------------------------------------------------------------- 20 | function newParser() 21 | 22 | XmlParser = {}; 23 | 24 | function XmlParser:ToXmlString(value) 25 | value = string.gsub(value, "&", "&"); -- '&' -> "&" 26 | value = string.gsub(value, "<", "<"); -- '<' -> "<" 27 | value = string.gsub(value, ">", ">"); -- '>' -> ">" 28 | value = string.gsub(value, "\"", """); -- '"' -> """ 29 | value = string.gsub(value, "([^%w%&%;%p%\t% ])", 30 | function(c) 31 | return string.format("&#x%X;", string.byte(c)) 32 | end); 33 | return value; 34 | end 35 | 36 | function XmlParser:FromXmlString(value) 37 | value = string.gsub(value, "&#x([%x]+)%;", 38 | function(h) 39 | return string.char(tonumber(h, 16)) 40 | end); 41 | value = string.gsub(value, "&#([0-9]+)%;", 42 | function(h) 43 | return string.char(tonumber(h, 10)) 44 | end); 45 | value = string.gsub(value, """, "\""); 46 | value = string.gsub(value, "'", "'"); 47 | value = string.gsub(value, ">", ">"); 48 | value = string.gsub(value, "<", "<"); 49 | value = string.gsub(value, "&", "&"); 50 | return value; 51 | end 52 | 53 | function XmlParser:ParseArgs(node, s) 54 | string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a) 55 | node:addProperty(w, self:FromXmlString(a)) 56 | end) 57 | end 58 | 59 | function XmlParser:ParseXmlText(xmlText) 60 | local stack = {} 61 | local top = newNode() 62 | table.insert(stack, top) 63 | local ni, c, label, xarg, empty 64 | local i, j = 1, 1 65 | while true do 66 | ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w_:]+)(.-)(%/?)>", i) 67 | if not ni then break end 68 | local text = string.sub(xmlText, i, ni - 1); 69 | if not string.find(text, "^%s*$") then 70 | local lVal = (top:value() or "") .. self:FromXmlString(text) 71 | stack[#stack]:setValue(lVal) 72 | end 73 | if empty == "/" then -- empty element tag 74 | local lNode = newNode(label) 75 | self:ParseArgs(lNode, xarg) 76 | top:addChild(lNode) 77 | elseif c == "" then -- start tag 78 | local lNode = newNode(label) 79 | self:ParseArgs(lNode, xarg) 80 | table.insert(stack, lNode) 81 | top = lNode 82 | else -- end tag 83 | local toclose = table.remove(stack) -- remove top 84 | 85 | top = stack[#stack] 86 | if #stack < 1 then 87 | error("XmlParser: nothing to close with " .. label) 88 | end 89 | if toclose:name() ~= label then 90 | error("XmlParser: trying to close " .. toclose.name .. " with " .. label) 91 | end 92 | top:addChild(toclose) 93 | end 94 | i = j + 1 95 | end 96 | local text = string.sub(xmlText, i); 97 | if #stack > 1 then 98 | error("XmlParser: unclosed " .. stack[#stack]:name()) 99 | end 100 | return top 101 | end 102 | 103 | function XmlParser:loadFile(path) 104 | -- if not base then 105 | -- base = system.ResourceDirectory 106 | -- end 107 | 108 | -- local path = system.pathForFile(xmlFilename, base) 109 | local hFile, err = io.open(path, "r"); 110 | 111 | if hFile and not err then 112 | local xmlText = hFile:read("*a"); -- read file content 113 | io.close(hFile); 114 | return self:ParseXmlText(xmlText), nil; 115 | else 116 | print(err) 117 | return nil 118 | end 119 | end 120 | 121 | return XmlParser 122 | end 123 | 124 | function newNode(name) 125 | local node = {} 126 | node.___value = nil 127 | node.___name = name 128 | node.___children = {} 129 | node.___props = {} 130 | 131 | function node:value() return self.___value end 132 | function node:setValue(val) self.___value = val end 133 | function node:name() return self.___name end 134 | function node:setName(name) self.___name = name end 135 | function node:children() return self.___children end 136 | function node:numChildren() return #self.___children end 137 | function node:addChild(child) 138 | if self[child:name()] ~= nil then 139 | if type(self[child:name()].name) == "function" then 140 | local tempTable = {} 141 | table.insert(tempTable, self[child:name()]) 142 | self[child:name()] = tempTable 143 | end 144 | table.insert(self[child:name()], child) 145 | else 146 | self[child:name()] = child 147 | end 148 | table.insert(self.___children, child) 149 | end 150 | 151 | function node:properties() return self.___props end 152 | function node:numProperties() return #self.___props end 153 | function node:addProperty(name, value) 154 | local lName = "@" .. name 155 | if self[lName] ~= nil then 156 | if type(self[lName]) == "string" then 157 | local tempTable = {} 158 | table.insert(tempTable, self[lName]) 159 | self[lName] = tempTable 160 | end 161 | table.insert(self[lName], value) 162 | else 163 | self[lName] = value 164 | end 165 | table.insert(self.___props, { name = name, value = self[name] }) 166 | end 167 | 168 | return node 169 | end 170 | -------------------------------------------------------------------------------- /src/C Change SUB resolution to match video PATCH.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 5 | 6 | Change SUB resolution to match video PATCH 7 | 8 | [Script Info] 9 | ; Script generated by Aegisub 3.2.2 10 | ; http://www.aegisub.org/ 11 | Title: Default Aegisub file 12 | ScriptType: v4.00+ 13 | WrapStyle: 0 14 | ScaledBorderAndShadow: no 15 | YCbCr Matrix: TV.709 16 | PlayResX: 384 17 | PlayResY: 288 18 | 19 | ]] 20 | 21 | --Script properties 22 | script_name="C Change SUB resolution to match video PATCH" 23 | script_description="Change SUB resolution to match video PATCH v1.2" 24 | script_author="chaaaaang" 25 | script_version="1.3" 26 | 27 | include('karaskel.lua') 28 | 29 | --GUI 30 | local dialog_config={ 31 | {class="label",label="input resolution",x=0,y=0,width=1}, 32 | {class="dropdown",name="i",items={"384x288","640x480","720x480","800x480","1024x576","1280x720","1440x810","1920x1080","3840x2160","7680x4320"},value="384x288",x=1,y=0}, 33 | {class="label",label="output resolution",x=0,y=1,width=1}, 34 | {class="dropdown",name="o",items={"384x288","640x480","720x480","800x480","1024x576","1280x720","1440x810","1920x1080","3840x2160","7680x4320"},value="1920x1080",x=1,y=1}, 35 | {class="checkbox",name="e",label="scale \\blur, \\be, \\bord and \\shad",value=false,x=0,y=2,width=2,hint="recommend: off"}, 36 | {class="checkbox",name="p",label="scale \\1img",value=false,x=0,y=3}, 37 | {class="label",label=" SUB Resolution\n Reset v1.3",x=1,y=3,height=2} 38 | } 39 | local buttons={"Run","Quit"} 40 | 41 | local function rounding(x) 42 | return tonumber(string.format('%.3f', x)) 43 | end 44 | 45 | function main(subtitle, selected) 46 | local meta,styles=karaskel.collect_head(subtitle,false) 47 | 48 | local pressed, result = aegisub.dialog.display(dialog_config,buttons) 49 | if pressed ~= "Run" then 50 | aegisub.cancel() 51 | else 52 | local iw,ih = result.i:match("(%d+)x(%d+)") 53 | local ow,oh = result.o:match("(%d+)x(%d+)") 54 | local rx,ry = tonumber(ow)/tonumber(iw),tonumber(oh)/tonumber(ih) 55 | 56 | for i=1,#subtitle do 57 | if subtitle[i].class=="style" then 58 | local style = subtitle[i] 59 | style.scale_x = rounding(style.scale_x*ry) 60 | style.scale_y = rounding(style.scale_y*ry) 61 | style.spacing = rounding(style.spacing*rx/ry) 62 | style.margin_t = rounding(style.margin_t*ry) 63 | style.margin_b = rounding(style.margin_b*ry) 64 | style.margin_l = rounding(style.margin_l*rx) 65 | style.margin_r = rounding(style.margin_r*rx) 66 | 67 | if result.e==true then 68 | style.outline = rounding(style.outline*ry) 69 | style.shadow = rounding(style.shadow*ry) 70 | end 71 | subtitle[i] = style 72 | elseif subtitle[i].class=="dialogue" and subtitle[i].comment==false then 73 | local line = subtitle[i] 74 | local linetext = line.text 75 | linetext = linetext:gsub("}{","") 76 | 77 | linetext = linetext:gsub("\\pos%( *([%d%.%-]+) *, *([%d%.%-]+) *%)", 78 | function(a,b) return "\\pos("..rounding(a*rx)..","..rounding(b*ry)..")" end) 79 | linetext = linetext:gsub("\\org%( *([%d%.%-]+) *, *([%d%.%-]+) *%)", 80 | function(a,b) return "\\org("..rounding(a*rx)..","..rounding(b*ry)..")" end) 81 | linetext = linetext:gsub("(\\movev?c?)%( *([%d%.%-]+) *, *([%d%.%-]+) *, *([%d%.%-]+) *, *([%d%.%-]+) *%)", 82 | function(p,a,b,c,d) 83 | return p.."("..rounding(a*rx)..","..rounding(b*ry)..","..rounding(c*rx)..","..rounding(d*ry)..")" 84 | end) 85 | -- moves 86 | linetext = linetext:gsub("\\moves(%d)(%([^%)]+%))", 87 | function(p,a) 88 | p = tonumber(p) 89 | a = a:gsub(" *([%d%.%-]+) *, *([%d%.%-]+) *", 90 | function (x,y) return rounding(x*rx)..","..rounding(y*ry) end, p) 91 | return "\\moves"..p..a 92 | end) 93 | -- clip 94 | linetext = linetext:gsub("(\\i?clip)(%([^%)]+%))", 95 | function (p,a) 96 | if a:match(",")~=nil then 97 | a = a:gsub(" *([%d%.%-]+) *, *([%d%.%-]+) *, *([%d%.%-]+) *, *([%d%.%-]+) *", function (h,j,k,l) 98 | return rounding(h*rx)..","..rounding(j*ry)..","..rounding(k*rx)..","..rounding(l*ry) end) 99 | else 100 | a = a:gsub("([%d%.%-]+) +([%d%.%-]+)", 101 | function (b,c) return rounding(b*rx).." "..rounding(c*ry) end) 102 | end 103 | return p..a 104 | end) 105 | 106 | linetext = linetext:gsub("\\fsp([%d%.%-]+)", function (a) return "\\fsp"..rounding(a*rx/ry) end) 107 | linetext = linetext:gsub("\\fsvp([%d%.%-]+)", function (a) return "\\fsvp"..rounding(a*ry) end) 108 | linetext = linetext:gsub("\\fsc([%d%.%-]+)", "\\fscx%1\\fscy%1") 109 | linetext = linetext:gsub("\\fscx([%d%.%-]+)", function (a) return "\\fscx"..rounding(a*ry) end) 110 | linetext = linetext:gsub("\\fscy([%d%.%-]+)", function (a) return "\\fscy"..rounding(a*ry) end) 111 | -- drawing 112 | if linetext:match("\\p%d")~=nil then 113 | if linetext:match("\\fscx")~=nil then 114 | linetext = linetext:gsub("\\fscx([%d%.%-]+)",function (a) return "\\fscx"..rounding(a*rx/ry) end) 115 | else 116 | karaskel.preproc_line(subtitle,meta,styles,line) 117 | linetext = linetext:gsub("^","{\\fscx"..rounding(line.styleref.scale_x*rx).."}") 118 | linetext = linetext:gsub("}{","") 119 | end 120 | -- 1img 121 | if result.p==true then 122 | linetext = linetext:gsub("\\1img","\\5img") 123 | end 124 | end 125 | if result.e==true then 126 | linetext = linetext:gsub("\\be([%d%.%-]+)",function (a) return "\\be"..rounding(a*ry) end) 127 | linetext = linetext:gsub("\\blur([%d%.%-]+)",function (a) return "\\blur"..rounding(a*ry) end) 128 | linetext = linetext:gsub("\\bord([%d%.%-]+)",function (a) return "\\bord"..rounding(a*ry) end) 129 | linetext = linetext:gsub("\\([xy]?shad)([%d%.%-]+)",function (a,b) return a..rounding(b*ry) end) 130 | end 131 | 132 | line.margin_t = rounding(line.margin_t*ry) 133 | line.margin_b = rounding(line.margin_b*ry) 134 | line.margin_l = rounding(line.margin_l*rx) 135 | line.margin_r = rounding(line.margin_r*rx) 136 | line.text = linetext 137 | subtitle[i]=line 138 | end 139 | aegisub.progress.set((i-1)/#subtitle*100) 140 | end 141 | end 142 | aegisub.log("The convertion has completed.\nPlease reset the resolution of the subtitle manually.") 143 | aegisub.set_undo_point(script_name) 144 | return selected 145 | end 146 | 147 | --Register macro (no validation function required) 148 | aegisub.register_macro(script_name,script_description,main) 149 | -------------------------------------------------------------------------------- /src/C Color Calibration.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 5 | 6 | ]] 7 | 8 | script_name="C Color Calibration" 9 | script_description="Color Calibration v1.2" 10 | script_author="chaaaaang" 11 | script_version="1.2" 12 | 13 | include('karaskel.lua') 14 | 15 | local function round(x) 16 | return math.floor(x + 0.5) 17 | end 18 | 19 | local function putmask(x) 20 | if x > 1 then 21 | x = 1 22 | elseif x < 0 then 23 | x = 0 24 | end 25 | return x 26 | end 27 | 28 | function config(subtitle, selected, active) 29 | local dialog_config = { 30 | {class='label', label='transfer function', x=0, y=0}, 31 | {class='dropdown', name='tf', items={'PQ', 'HLG'}, value='PQ', x=1, y=0}, 32 | {class='label', label='parameter', x=0, y=1}, 33 | {class='intedit', name='p1', value=30, x=1, y=1} 34 | } 35 | local buttons = {'Run', 'Quit'} 36 | 37 | local path = aegisub.decode_path('?user')..'\\ColorCalibration.txt' 38 | local file = io.open(path, 'r') 39 | if file ~= nil then 40 | dialog_config[2].value = file:read() 41 | dialog_config[4].value = tonumber(file:read()) 42 | file:close() 43 | end 44 | 45 | local pressed, result = aegisub.dialog.display(dialog_config,buttons) 46 | if pressed == 'Run' then 47 | file = io.open(path, 'w') 48 | file:write(result.tf .. '\n' .. result.p1 .. '\n') 49 | file:close() 50 | end 51 | aegisub.set_undo_point(script_name) 52 | return selected 53 | end 54 | 55 | function sdr2hdr(subtitle, selected, active) 56 | local function eotf_sRGB(v) 57 | if v <= 0.04045 then 58 | return v/12.92 59 | else 60 | return ((v + 0.055)/1.055)^2.4 61 | end 62 | end 63 | local function sRGB_to_RGB2020(r, g, b) 64 | local mat = { 65 | { 0.62744137, 0.32929746, 0.04335146}, 66 | { 0.06902762, 0.91958067, 0.01136142}, 67 | { 0.01636424, 0.08801716, 0.89556497}, 68 | } 69 | r = mat[1][1]*r + mat[1][2]*g + mat[1][3]*b 70 | g = mat[2][1]*r + mat[2][2]*g + mat[2][3]*b 71 | b = mat[3][1]*r + mat[3][2]*g + mat[3][3]*b 72 | return r, g, b 73 | end 74 | local function oetf_PQ_BT2100(Y) 75 | local c1, c2, c3 = 107/128, 2413/128, 2392/128 76 | local m1, m2 = 1305/8192, 2523/32 77 | Y = 59.5208 * Y 78 | -- OETF BT709 79 | if Y < 0.018 then 80 | Y = 4.5 * Y 81 | else 82 | Y = 1.099 * Y^0.45 - 0.099 83 | end 84 | -- EOTF BT1884 85 | Y = 100 * math.max(0, Y)^2.4 / 10000 86 | -- OOTF PQ BT2100 87 | return ((c1 + c2 * Y^m1) / (1 + c3 * Y^m1))^m2 88 | end 89 | local function oetf_HLG_BT2100(E) 90 | if E <= 1/12 then 91 | return math.sqrt(3*E) 92 | else 93 | local a = 0.17883277 94 | local b = 0.28466892 95 | local c = 0.55991073 96 | return a * math.log(12*E - b) + c 97 | end 98 | end 99 | local function RGB2020_to_YCbCr(R, G, B) 100 | local Kr, Kb = 0.2627, 0.0593 -- BT2020 101 | local Y_min, Y_max, C_min, C_max = 16/255, 235/255, 16/255, 240/255 102 | 103 | local Y = Kr * R + (1 - Kr - Kb) * G + Kb * B 104 | local Cb = 0.5 * (B - Y) / (1 - Kb) 105 | local Cr = 0.5 * (R - Y) / (1 - Kr) 106 | Y = Y * (Y_max - Y_min) + Y_min 107 | Cb = Cb * (C_max - C_min) 108 | Cr = Cr * (C_max - C_min) 109 | Cb = Cb + (C_max + C_min) / 2 110 | Cr = Cr + (C_max + C_min) / 2 111 | return Y, Cb, Cr 112 | end 113 | local function YCbCr_to_RGB709(Y, Cb, Cr) 114 | local Kr, Kb = 0.2126, 0.0722 -- BT709 115 | local Y_min, Y_max, C_min, C_max = 16/255, 235/255, 16/255, 240/255 116 | 117 | Y = Y - Y_min 118 | Cb = Cb - (C_max + C_min) / 2 119 | Cr = Cr - (C_max + C_min) / 2 120 | Y = Y / (Y_max - Y_min) 121 | Cb = Cb / (C_max - C_min) 122 | Cr = Cr / (C_max - C_min) 123 | local R = Y + (2 - 2 * Kr) * Cr 124 | local B = Y + (2 - 2 * Kb) * Cb 125 | local G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb) 126 | 127 | return R, G, B 128 | end 129 | local function color(c) 130 | local len = string.len(c) 131 | local a = '' 132 | if len == 8 then 133 | a = string.sub(c, 1, 2) 134 | c = string.sub(c, 3) 135 | elseif len == 6 then 136 | else 137 | return c 138 | end 139 | local b, g, r = c:match('(%x%x)(%x%x)(%x%x)') 140 | b = tonumber(b, 16)/255 141 | g = tonumber(g, 16)/255 142 | r = tonumber(r, 16)/255 143 | 144 | b = eotf_sRGB(b) 145 | g = eotf_sRGB(g) 146 | r = eotf_sRGB(r) 147 | 148 | r, g, b = sRGB_to_RGB2020(r, g, b) 149 | 150 | r, g, b = r/params[1], g/params[1], b/params[1] 151 | 152 | if transferfunc == 'PQ' then 153 | r = oetf_PQ_BT2100(r) 154 | g = oetf_PQ_BT2100(g) 155 | b = oetf_PQ_BT2100(b) 156 | else 157 | r = oetf_PQ_BT2100(r) 158 | g = oetf_PQ_BT2100(g) 159 | b = oetf_PQ_BT2100(b) 160 | end 161 | r, g, b = YCbCr_to_RGB709(RGB2020_to_YCbCr(r, g, b)) 162 | r = round(r * 255) 163 | g = round(g * 255) 164 | b = round(b * 255) 165 | return string.format('&H%s%02X%02X%02X&', a, b, g, r) 166 | end 167 | 168 | -- count 169 | local N = #selected 170 | -- params 171 | local path = aegisub.decode_path('?user')..'\\ColorCalibration.txt' 172 | local file = io.open(path, 'r') 173 | transferfunc = nil 174 | params = {} 175 | if file ~= nil then 176 | transferfunc = file:read() 177 | table.insert(params, tonumber(file:read())) 178 | file:close() 179 | else 180 | transferfunc = 'PQ' 181 | params = {30} 182 | end 183 | 184 | for si, li in ipairs(selected) do 185 | local line = subtitle[li] 186 | local linetext = line.text 187 | 188 | linetext = linetext:gsub('(\\[1234]?c)&?H?([%x]+)&?', function (pre, c) 189 | return pre .. color(c) 190 | end) 191 | linetext = linetext:gsub('(\\[1234]vc)%( *&?H?([%x]+)&? *, *&?H?([%x]+)&? *, *&?H?([%x]+)&? *, *&?H?([%x]+)&? *%)', 192 | function (pre, c1, c2, c3, c4) 193 | return string.format('%s(%s,%s,%s,%s)', pre, color(c1), color(c2), color(c3), color(c4)) 194 | end) 195 | 196 | line.text = linetext 197 | subtitle[li] = line 198 | aegisub.progress.set(si/N * 100) 199 | end 200 | 201 | aegisub.set_undo_point(script_name) 202 | return selected 203 | end 204 | 205 | function hdr2sdr(subtitle, selected, active) 206 | local function RGB709_to_YCbCr(R, G, B) 207 | local Kr, Kb = 0.2126, 0.0722 -- BT2020 208 | local Y_min, Y_max, C_min, C_max = 16/255, 235/255, 16/255, 240/255 209 | 210 | local Y = Kr * R + (1 - Kr - Kb) * G + Kb * B 211 | local Cb = 0.5 * (B - Y) / (1 - Kb) 212 | local Cr = 0.5 * (R - Y) / (1 - Kr) 213 | Y = Y * (Y_max - Y_min) + Y_min 214 | Cb = Cb * (C_max - C_min) 215 | Cr = Cr * (C_max - C_min) 216 | Cb = Cb + (C_max + C_min) / 2 217 | Cr = Cr + (C_max + C_min) / 2 218 | return Y, Cb, Cr 219 | end 220 | local function YCbCr_to_RGB2020(Y, Cb, Cr) 221 | local Kr, Kb = 0.2627, 0.0593 -- BT709 222 | local Y_min, Y_max, C_min, C_max = 16/255, 235/255, 16/255, 240/255 223 | 224 | Y = Y - Y_min 225 | Cb = Cb - (C_max + C_min) / 2 226 | Cr = Cr - (C_max + C_min) / 2 227 | Y = Y / (Y_max - Y_min) 228 | Cb = Cb / (C_max - C_min) 229 | Cr = Cr / (C_max - C_min) 230 | local R = Y + (2 - 2 * Kr) * Cr 231 | local B = Y + (2 - 2 * Kb) * Cb 232 | local G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb) 233 | 234 | return R, G, B 235 | end 236 | local function oetf_inverse_PQ_BT2100(Y) 237 | local c1, c2, c3 = 107/128, 2413/128, 2392/128 238 | local m1, m2 = 1305/8192, 2523/32 239 | 240 | -- ST1884 241 | Vp = Y^(1/m2) 242 | Y = math.max(0, Vp - c1) 243 | Y = 10000 * (Y / (c2 - c3 * Vp))^(1/m1) / 100 244 | -- EOTF INVERSE BT1886 245 | Y = Y^(1/2.4) 246 | -- OETF INVERSE BT709 247 | if Y < 0.081 then 248 | Y = Y / 4.5 249 | else 250 | Y = ((Y + 0.099) / 1.099)^(1/0.45) 251 | end 252 | return Y / 59.5208 253 | end 254 | local function RGB2020_to_sRGB(r, g, b) 255 | local mat = { 256 | { 1.66030349, -0.58757014, -0.07289006}, 257 | {-0.12437560, 1.13283448, -0.00835974}, 258 | {-0.01811228, -0.10058361, 1.11877033}, 259 | } 260 | r = mat[1][1]*r + mat[1][2]*g + mat[1][3]*b 261 | g = mat[2][1]*r + mat[2][2]*g + mat[2][3]*b 262 | b = mat[3][1]*r + mat[3][2]*g + mat[3][3]*b 263 | return r, g, b 264 | end 265 | local function eotf_inverse_sRGB(v) 266 | if v <= 0.0031308 then 267 | return v * 12.92 268 | else 269 | return 1.055 * v^(1/2.4) - 0.055 270 | end 271 | end 272 | local function color(c) 273 | local len = string.len(c) 274 | local a = '' 275 | if len == 8 then 276 | a = string.sub(c, 1, 2) 277 | c = string.sub(c, 3) 278 | elseif len == 6 then 279 | else 280 | return c 281 | end 282 | local b, g, r = c:match('(%x%x)(%x%x)(%x%x)') 283 | b = tonumber(b, 16)/255 284 | g = tonumber(g, 16)/255 285 | r = tonumber(r, 16)/255 286 | 287 | r, g, b = YCbCr_to_RGB2020(RGB709_to_YCbCr(r, g, b)) 288 | r, g, b = putmask(r), putmask(g), putmask(b) 289 | 290 | if transferfunc == 'PQ' then 291 | r = oetf_inverse_PQ_BT2100(r) 292 | g = oetf_inverse_PQ_BT2100(g) 293 | b = oetf_inverse_PQ_BT2100(b) 294 | else 295 | r = oetf_inverse_PQ_BT2100(r) 296 | g = oetf_inverse_PQ_BT2100(g) 297 | b = oetf_inverse_PQ_BT2100(b) 298 | end 299 | r, g, b = r*params[1], g*params[1], b*params[1] 300 | r, g, b = putmask(r), putmask(g), putmask(b) 301 | 302 | r, g, b = RGB2020_to_sRGB(r, g, b) 303 | r, g, b = putmask(r), putmask(g), putmask(b) 304 | 305 | b = eotf_inverse_sRGB(b) 306 | g = eotf_inverse_sRGB(g) 307 | r = eotf_inverse_sRGB(r) 308 | 309 | r = round(r * 255) 310 | g = round(g * 255) 311 | b = round(b * 255) 312 | return string.format('&H%s%02X%02X%02X&', a, b, g, r) 313 | end 314 | 315 | -- count 316 | local N = #selected 317 | -- params 318 | local path = aegisub.decode_path('?user')..'\\ColorCalibration.txt' 319 | local file = io.open(path, 'r') 320 | transferfunc = nil 321 | params = {} 322 | if file ~= nil then 323 | transferfunc = file:read() 324 | table.insert(params, tonumber(file:read())) 325 | file:close() 326 | else 327 | transferfunc = 'PQ' 328 | params = {30} 329 | end 330 | 331 | for si, li in ipairs(selected) do 332 | local line = subtitle[li] 333 | local linetext = line.text 334 | 335 | linetext = linetext:gsub('(\\[1234]?c)&?H?([%x]+)&?', function (pre, c) 336 | return pre .. color(c) 337 | end) 338 | linetext = linetext:gsub('(\\[1234]vc)%( *&?H?([%x]+)&? *, *&?H?([%x]+)&? *, *&?H?([%x]+)&? *, *&?H?([%x]+)&? *%)', 339 | function (pre, c1, c2, c3, c4) 340 | return string.format('%s(%s,%s,%s,%s)', pre, color(c1), color(c2), color(c3), color(c4)) 341 | end) 342 | 343 | line.text = linetext 344 | subtitle[li] = line 345 | aegisub.progress.set(si/N * 100) 346 | end 347 | 348 | aegisub.set_undo_point(script_name) 349 | return selected 350 | end 351 | 352 | function macro_validation(subtitle, selected, active) 353 | return true 354 | end 355 | 356 | --This is what puts your automation in Aegisub's automation list 357 | aegisub.register_macro(script_name..'/sdr2hdr', script_description, sdr2hdr, macro_validation) 358 | aegisub.register_macro(script_name..'/hdr2sdr', script_description, hdr2sdr, macro_validation) 359 | aegisub.register_macro(script_name..'/config', script_description, config, macro_validation) -------------------------------------------------------------------------------- /src/C Fast Tools.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 5 | 6 | ]] 7 | 8 | --Script properties 9 | script_name="C Fast Tools" 10 | script_description="Fast Tools v1.2.1" 11 | script_author="chaaaaang" 12 | script_version="1.2.1" 13 | 14 | clipboard = require 'aegisub.clipboard' 15 | 16 | function fast_clip_iclip_converter(subtitle, selected) 17 | for si,li in ipairs(selected) do 18 | local line=subtitle[li] 19 | local linetext = line.text 20 | linetext = linetext:gsub("(\\i?clip)",function (a) 21 | if a:match("iclip")~=nil then 22 | return "\\clip" 23 | else 24 | return "\\iclip" 25 | end 26 | end) 27 | line.text = linetext 28 | subtitle[li]=line 29 | end 30 | aegisub.set_undo_point(script_name) 31 | return selected 32 | end 33 | 34 | function fast_copy(subtitle, selected) 35 | 36 | local data = clipboard.get() 37 | local data_table = {} 38 | data = data.."\n" 39 | 40 | for i in data:gmatch("(.-)\n") do 41 | local layer,style,l,r,v,effect,text 42 | 43 | layer = tonumber(i:match("(%d+),")) 44 | i = i:gsub("[^,]*,","",3) 45 | style = i:match("(.-),") 46 | i = i:gsub("[^,]*,","",2) 47 | l = tonumber(i:match("(.-),")) 48 | i = i:gsub("[^,]*,","",1) 49 | r = tonumber(i:match("(.-),")) 50 | i = i:gsub("[^,]*,","",1) 51 | v = tonumber(i:match("(.-),")) 52 | i = i:gsub("[^,]*,","",1) 53 | effect = i:match("(.-),") 54 | i = i:gsub("[^,]*,","",1) 55 | text = i 56 | -- clear the mocha stuff 57 | i = i:gsub("^{=%d+}","") 58 | table.insert(data_table,{ly=layer,s=style,l=l,r=r,v=v,e=effect,t=text}) 59 | end 60 | 61 | for si,li in ipairs(selected) do 62 | if si>#data_table then break end 63 | local line=subtitle[li] 64 | local linetext = line.text 65 | linetext = data_table[si].t 66 | line.text = linetext 67 | 68 | line.layer = data_table[si].ly 69 | line.style = data_table[si].s 70 | line.margin_l = data_table[si].l 71 | line.margin_r = data_table[si].r 72 | 73 | subtitle[li]=line 74 | 75 | if si==#selected and si<#data_table then 76 | if li~=#subtitle then 77 | for j = 1,#data_table-si do 78 | subtitle.insert(li+j,line) 79 | local new_line = subtitle[li+j] 80 | new_line.text = data_table[si+j].t 81 | 82 | new_line.layer = data_table[si+j].ly 83 | new_line.style = data_table[si+j].s 84 | new_line.margin_l = data_table[si+j].l 85 | new_line.margin_r = data_table[si+j].r 86 | 87 | subtitle[li+j]=new_line 88 | end 89 | else 90 | for j = 1,#data_table-si do 91 | subtitle.append(line) 92 | local new_line = subtitle[li+j] 93 | new_line.text = data_table[si+j].t 94 | 95 | new_line.layer = data_table[si+j].ly 96 | new_line.style = data_table[si+j].s 97 | new_line.margin_l = data_table[si+j].l 98 | new_line.margin_r = data_table[si+j].r 99 | 100 | subtitle[li+j]=new_line 101 | end 102 | end 103 | end 104 | end 105 | 106 | aegisub.set_undo_point(script_name) 107 | return selected 108 | end 109 | 110 | function fast_enter(subtitle, selected) 111 | for si,li in ipairs(selected) do 112 | local line=subtitle[li] 113 | local linetext = line.text 114 | linetext = linetext.."\\N" 115 | line.text = linetext 116 | subtitle[li]=line 117 | end 118 | aegisub.set_undo_point(script_name) 119 | return selected 120 | end 121 | 122 | function fast_fad_seq_in(subtitle, selected) 123 | local N = #selected 124 | local all = subtitle[selected[N]].end_time - subtitle[selected[1]].start_time 125 | local init = subtitle[selected[1]].start_time 126 | for si,li in ipairs(selected) do 127 | local line = subtitle[li] 128 | local time = round((aegisub.ms_from_frame(aegisub.frame_from_ms(line.start_time))+aegisub.ms_from_frame(aegisub.frame_from_ms(line.end_time)))/2) 129 | local t = time - line.start_time 130 | local i = time - init 131 | local x = round(t/(i/all)) 132 | line.text = line.text:gsub("^{","{\\fad("..x..",0)") 133 | subtitle[li] = line 134 | end 135 | end 136 | 137 | function fast_fad_seq_out(subtitle, selected) 138 | local N = #selected 139 | local all = subtitle[selected[N]].end_time - subtitle[selected[1]].start_time 140 | local init = subtitle[selected[N]].end_time 141 | for si,li in ipairs(selected) do 142 | local line = subtitle[li] 143 | local time = round((aegisub.ms_from_frame(aegisub.frame_from_ms(line.start_time))+aegisub.ms_from_frame(aegisub.frame_from_ms(line.end_time)))/2) 144 | local t = line.end_time - time 145 | local i = init - time 146 | local x = round(t/(i/all)) 147 | line.text = line.text:gsub("^{","{\\fad(0,"..x..")") 148 | subtitle[li] = line 149 | end 150 | end 151 | 152 | function fast_selection_onward(subtitle, selected) 153 | local tt = {} 154 | for i=selected[1],#subtitle do 155 | table.insert(tt, i) 156 | end 157 | return tt 158 | end 159 | 160 | function round(x) 161 | return math.floor(x+0.5) 162 | end 163 | 164 | --Register macro (no validation function required) 165 | aegisub.register_macro(script_name.."/fast_clip_iclip_converter",script_description,fast_clip_iclip_converter) 166 | aegisub.register_macro(script_name.."/fast_copy",script_description,fast_copy) 167 | aegisub.register_macro(script_name.."/fast_enter",script_description,fast_enter) 168 | aegisub.register_macro(script_name.."/fast_fad_sequence(in)",script_description,fast_fad_seq_in) 169 | aegisub.register_macro(script_name.."/fast_fad_sequence(out)",script_description,fast_fad_seq_out) 170 | aegisub.register_macro(script_name.."/fast_selection_onward",script_description,fast_selection_onward) -------------------------------------------------------------------------------- /src/C Font Resize.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 5 | 6 | Font Resizing (Mocha Deshaking) 7 | 8 | Feature: 9 | Desize the \fs and correspondingly Amplify the \fscx \fscy to make the size of text seemingly the same. 10 | Now \fs \fscx \fscy \fsc \fsp supported. 11 | e.g. \fs50 -> \fs5\fscx1000\fscy1000 12 | It works well in deshaking Mocha, try it if you don't believe it. 13 | 14 | Manual: 15 | 1. Select the line(s) and run the script, run it before or after applying Mocha 16 | 17 | Bug Report: 18 | 1. \t(\fs) is not supported 19 | 20 | Updated on 21st Jan 2021 21 | inline scale mistake fixed 22 | fsp keeps to 3 decimal places 23 | ]] 24 | 25 | script_name="C Font Resizing (Mocha Deshaking)" 26 | script_description="Font Resizing (Mocha Deshaking) v1.3" 27 | script_author="lyger modified by chaaaaang" 28 | script_version="1.3" 29 | 30 | include("karaskel.lua") 31 | 32 | function refont(sub, sel) 33 | 34 | local meta,styles=karaskel.collect_head(sub,false) 35 | 36 | for si,li in ipairs(sel) do 37 | 38 | local line=sub[li] 39 | 40 | karaskel.preproc_line(sub,meta,styles,line) 41 | 42 | --The next few steps will break the line up into tag-text pairs 43 | 44 | --First ensures that the line begins with an override tag block 45 | --x = A and B or C means if A, then x = B, else x = C 46 | ltext=(line.text:match("^{")==nil) and "{}"..line.text or line.text 47 | 48 | --each closed curly brace, otherwise adjacent override blocks with no text in between won't match 49 | ltext=ltext:gsub("}{","") --new 50 | ltext=ltext:gsub("\\fsc([%d%.]+)","\\fscx%1\\fscy%1") 51 | 52 | --Then ensure that the first tag includes \fs \fscx \fscy tag 53 | if ltext:match("^{[^}]*\\fs[%d%.]+[^}]*}")==nil then 54 | ltext=ltext:gsub("^{",string.format("{\\fs%.1f",line.styleref.fontsize)) 55 | end 56 | if ltext:match("^{[^}]*\\fscy[%d%.]+[^}]*}")==nil then 57 | ltext=ltext:gsub("^{",string.format("{\\fscy%.2f",line.styleref.scale_y)) 58 | end 59 | if ltext:match("^{[^}]*\\fscx[%d%.]+[^}]*}")==nil then 60 | ltext=ltext:gsub("^{",string.format("{\\fscx%.2f",line.styleref.scale_x)) 61 | end 62 | 63 | --These store the current values of the three parameters at the part of the line 64 | --we are looking at 65 | --Since we have not started looking at the line yet, these are set to the style defaults 66 | cur_fs=line.styleref.fontsize 67 | cur_fscx=line.styleref.scale_x 68 | cur_fscy=line.styleref.scale_y 69 | cur_fsp=0. 70 | if (line.styleref.spacing~=0) then 71 | if ltext:match("^{[^}]*\\fsp[%d%.%-]+[^}]*}")==nil then 72 | ltext=ltext:gsub("^{",string.format("{\\fsp%.3f",line.styleref.spacing)) 73 | end 74 | cur_fsp=line.styleref.spacing 75 | end 76 | 77 | -- vector picture 78 | if (ltext:match("\\p%d")~=nil) then 79 | ltext = ltext:gsub("\\p(%d)",function (a) return "\\p"..a+3 end) 80 | ltext = ltext:gsub("\\fscx([%d%.]+)",function (a) return "\\fscx"..a*8 end) 81 | ltext = ltext:gsub("\\fscy([%d%.]+)",function (a) return "\\fscy"..a*8 end) 82 | if (ltext:match("{\\p%d}$")~=nil) then 83 | ltext = ltext:gsub("{\\p%d}$","{\\p0}") 84 | end 85 | line.text = ltext 86 | sub[li] = line 87 | goto loop_end 88 | end 89 | 90 | --Store these pairs in a new data structure 91 | tt_table={} 92 | for tg,tx in ltext:gmatch("({[^}]*})([^{]*)") do 93 | table.insert(tt_table,{tag=tg,text=tx}) 94 | end 95 | 96 | --This is where the new text will be stored 97 | rebuilt_text="" 98 | 99 | --Now rebuild the line piece-by-piece using the tag-text pairs stored in tt_table 100 | for _,tt in ipairs(tt_table) do 101 | --x = A or B means x = A if A is not nil, otherwise x = B 102 | 103 | --first handle the \fs tag 104 | local n_fs="" --PLEASE MAKE SURE NO \t(\fs) CODE IS USED 105 | local i=0 106 | for nfs in tt.tag:gmatch("\\fs[%d%.]+") do 107 | n_fs=nfs 108 | i=i+1 109 | end 110 | if (i>=2) then 111 | tt.tag:gsub("\\fs[%d%.]+","") 112 | tt.tag:gsub("^{",string.format("{%s",n_fs)) 113 | end 114 | if (i>=1) then 115 | cur_fs=tonumber(n_fs:match("[%d%.]+")) 116 | end 117 | 118 | new_fs=(cur_fs>10) and math.floor(cur_fs/10) or 1 119 | factor=cur_fs/new_fs 120 | tt.tag=tt.tag:gsub("\\fs[%d%.]+", "\\fs"..new_fs) 121 | 122 | --make sure there is fscx fscy in the tag 123 | local tagcopy = tt.tag 124 | tagcopy = tagcopy:gsub("\\t%([^%)]*\\fsc[xy][^%)]*%)","") 125 | if (tagcopy:match("\\fscy")==nil) then tt.tag = tt.tag:gsub("^{",string.format("{\\fscy%d",cur_fscy)) end 126 | 127 | if (tagcopy:match("\\fscx")==nil) then tt.tag = tt.tag:gsub("^{",string.format("{\\fscx%d",cur_fscx)) end 128 | 129 | --*new* delete all blanks and readd blanks behind \fscx \fscy \fsp use 'WWW' 130 | tt.tag=tt.tag:gsub("(\\fscx[%d%.%-]+)","%1}") 131 | tt.tag=tt.tag:gsub("(\\fscy[%d%.%-]+)","%1}") 132 | tt.tag=tt.tag:gsub("(\\fsp[%d%.%-]+)","%1}") 133 | -- table tgs means tagsplit 134 | local rebuilt_tag="" 135 | for tgs in tt.tag:gmatch("([^}]+)}") do 136 | if (tgs:match("\\fscx[%d%.%-]+")~=nil) then 137 | cur_fscx=tonumber(tgs:match("\\fscx([%d%.%-]+)")) 138 | new_fscx=math.floor(cur_fscx*factor) 139 | tgs=tgs:gsub("\\fscx[%d%.%-]+",string.format("\\fscx%d",new_fscx)) 140 | end 141 | if (tgs:match("\\fscy[%d%.%-]+")~=nil) then 142 | cur_fscy=tonumber(tgs:match("\\fscy([%d%.%-]+)")) 143 | new_fscy=math.floor(cur_fscy*factor) 144 | tgs=tgs:gsub("\\fscy[%d%.%-]+",string.format("\\fscy%d",new_fscy)) 145 | end 146 | if (tgs:match("\\fsp[%d%.%-]+")~=nil) then 147 | cur_fsp=tonumber(tgs:match("\\fsp([%-%d%.]+)")) 148 | new_fsp=cur_fsp/factor 149 | tgs=tgs:gsub("\\fsp[%d%.%-]+",string.format("\\fsp%.3f",new_fsp)) 150 | end 151 | rebuilt_tag = rebuilt_tag..tgs 152 | end 153 | tt.tag = rebuilt_tag.."}" 154 | rebuilt_text=rebuilt_text..tt.tag..tt.text 155 | end 156 | 157 | line.text=rebuilt_text 158 | 159 | sub[li]=line 160 | 161 | ::loop_end:: 162 | 163 | end 164 | 165 | --Set undo point and maintain selection 166 | aegisub.set_undo_point(script_name) 167 | return sel 168 | 169 | end 170 | 171 | --Register macro 172 | aegisub.register_macro(script_name,script_description,refont) -------------------------------------------------------------------------------- /src/C Jump.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 5 | 6 | ]] 7 | 8 | script_name="C Jump" 9 | script_description="Jump v1.0" 10 | script_author="chaaaaang" 11 | script_version="1.0" 12 | 13 | include('karaskel.lua') 14 | 15 | local dialog_config = { 16 | {class="checkbox",label="backward",name="backward",x=0,y=0},--1 17 | {class="checkbox",label="forward",name="forward",x=1,y=0},--2 18 | {class="label",label="frame",x=0,y=1},--3 19 | {class="intedit",name="frame",value=0,x=1,y=1},--4 20 | {class="label",label="time",x=0,y=2},--5 21 | {class="edit",name="time",value="",x=1,y=2,hint="h:mm:ss:msmsms,0 at high digits can be omitted"}--6 22 | } 23 | local buttons = {"Find Frame","Find Time","Jump out of Mocha Lines","Quit"} 24 | 25 | --This is the main processing function that modifies the subtitles 26 | function main(subtitle, selected, active) 27 | local meta,styles=karaskel.collect_head(subtitle,false) 28 | local xres, yres, ar, artype = aegisub.video_size() 29 | local data,tt = {},{} 30 | 31 | -- count 32 | local N,n,count = #subtitle,#selected,0 33 | local line_selected 34 | 35 | -- change the content shown in UI 36 | local timeS,timeE = 0,0 37 | for si,li in ipairs(selected) do 38 | local line = subtitle[li] 39 | karaskel.preproc_line(subtitle,meta,styles,line) 40 | if line.comment==false then 41 | if si==1 then 42 | timeS,timeE = line.start_time,line.end_time 43 | line_selected = li 44 | else 45 | timeS,timeE = math.min(timeS,line.start_time),math.max(timeE,line.end_time) 46 | end 47 | table.insert(data,line.text_stripped) 48 | end 49 | end 50 | local frameS,frameE = aegisub.frame_from_ms(timeS),aegisub.frame_from_ms(timeE) 51 | dialog_config[4].value = math.floor((frameS+frameE)/2) 52 | local time = math.floor((timeS+timeE)/2) 53 | local hour,minute,second,millisecond = math.floor(time/1000/60/60),math.floor(time%3600000/1000/60),math.floor(time%60000/1000),time%1000 54 | dialog_config[6].value = hour..":"..minute..":"..second..":"..millisecond 55 | 56 | -- UI 57 | local pressed, result = aegisub.dialog.display(dialog_config,buttons) 58 | if (pressed=="Quit") then aegisub.cancel() end 59 | 60 | ::loop_start:: 61 | 62 | if result.forward==true and result.backward==false then 63 | -- in case select the last line 64 | if line_selected+n>N then line_selected=-1*n+1 end 65 | 66 | for i=line_selected+n,N do 67 | local line = subtitle[i] 68 | if line.class=="dialogue" and line.comment==false then 69 | karaskel.preproc_line(subtitle,meta,styles,line) 70 | 71 | local ts,te = line.start_time,line.end_time 72 | if pressed=="Find Frame" then 73 | local fs,fe = aegisub.frame_from_ms(ts),aegisub.frame_from_ms(te) 74 | if fs<=result.frame and fe>=result.frame then 75 | table.insert(tt,i) 76 | aegisub.set_undo_point(script_name) 77 | return tt 78 | end 79 | elseif pressed=="Find Time" then 80 | local h,m,s,ms = result.time:match("(%d*):(%d*):(%d*):(%d*)") 81 | h,m,s,ms = tonumber(h),tonumber(m),tonumber(s),tonumber(ms) 82 | local t = h*1000*60*60 + m*1000*60 + s*1000 + ms 83 | if ts<=t and te>=t then 84 | table.insert(tt,i) 85 | aegisub.set_undo_point(script_name) 86 | return tt 87 | end 88 | elseif pressed=="Jump out of Mocha Lines" then 89 | local same = false 90 | for __,j in ipairs(data) do 91 | if line.text_stripped==j then 92 | same = true 93 | break 94 | end 95 | end 96 | if same==false then 97 | table.insert(tt,i) 98 | aegisub.set_undo_point(script_name) 99 | return tt 100 | end 101 | end 102 | end 103 | 104 | --circle 105 | if i == N then 106 | if count==1 then break end 107 | count,line_selected = 1,-1*n+1 108 | goto loop_start 109 | end 110 | end 111 | elseif result.forward==false and result.backward==true then 112 | for i=line_selected-1,1,-1 do 113 | local line = subtitle[i] 114 | if line.class=="dialogue" and line.comment==false then 115 | karaskel.preproc_line(subtitle,meta,styles,line) 116 | 117 | local ts,te = line.start_time,line.end_time 118 | if pressed=="Find Frame" then 119 | local fs,fe = aegisub.frame_from_ms(ts),aegisub.frame_from_ms(te) 120 | if fs<=result.frame and fe>=result.frame then 121 | table.insert(tt,i) 122 | aegisub.set_undo_point(script_name) 123 | return tt 124 | end 125 | elseif pressed=="Find Time" then 126 | local h,m,s,ms = result.time:match("(%d*):(%d*):(%d*):(%d*)") 127 | h,m,s,ms = tonumber(h),tonumber(m),tonumber(s),tonumber(ms) 128 | local t = h*1000*60*60 + m*1000*60 + s*1000 + ms 129 | if ts<=t and te>=t then 130 | table.insert(tt,i) 131 | aegisub.set_undo_point(script_name) 132 | return tt 133 | end 134 | elseif pressed=="Jump out of Mocha Lines" then 135 | local same = false 136 | for __,j in ipairs(data) do 137 | if line.text_stripped==j then 138 | same = true 139 | break 140 | end 141 | end 142 | if same==false then 143 | table.insert(tt,i) 144 | aegisub.set_undo_point(script_name) 145 | return tt 146 | end 147 | end 148 | end 149 | 150 | --circle 151 | if i == N then 152 | if count==1 then break end 153 | count,line_selected = 1,N+1 154 | goto loop_start 155 | end 156 | end 157 | else 158 | aegisub.cancel() 159 | end 160 | 161 | aegisub.set_undo_point(script_name) 162 | return selected 163 | end 164 | 165 | function round(x) 166 | return math.floor(x+0.5) 167 | end 168 | 169 | function macro_validation(subtitle, selected, active) 170 | return true 171 | end 172 | 173 | --This is what puts your automation in Aegisub's automation list 174 | aegisub.register_macro(script_name,script_description,main,macro_validation) -------------------------------------------------------------------------------- /src/C Merge Bilingual SUBS.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | Merge Bilingual SUBS 5 | 6 | Feature: 7 | Move english SUBS in the previous/next line to this line 8 | 9 | Manual: 10 | Select the line and hit the hotkey (If you don't use hotkeys, the script is useless) 11 | 12 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 13 | 14 | ]] 15 | 16 | --Script properties 17 | script_name="C Merge Bilingual SUBS" 18 | script_description="Merge Bilingual SUBS v1.2" 19 | script_author="chaaaaang" 20 | script_version="1.2" 21 | 22 | include('karaskel.lua') 23 | local re = require('aegisub.re') 24 | 25 | function better_bilang_combiner(subtitle, selected) 26 | local dialog_config = { 27 | {class='label', label='Better Bilang Combiner v1.3', x=0, y=0, width=4}, 28 | {class='label', label='Selected: ', x=0, y=1, width=2}, 29 | {class='dropdown',name='sel', items={'Chinese Lines','English Lines'}, value='Chinese Lines', x=2, y=1,width=2}, 30 | {class='label', label='Timeline based on:', x=0, y=2, width=3}, 31 | {class='dropdown', name='time', items={'Chinese', 'English'}, value='Chinese', x=3, y=2}, 32 | 33 | {class='label', label='delete', x=0, y=3}, 34 | {class='checkbox', label='...', name='deldots', value=true, x=1, y=3}, 35 | {class='checkbox', label='--', name='delbars', value=true, x=2, y=3}, 36 | {class='checkbox', label='{}', name='delkets', value=true, x=3, y=3}, 37 | 38 | {class='label', label='ignore', x=0, y=4}, 39 | {class='checkbox', label='*', name='ignstar', value=true, x=1, y=4}, 40 | {class='checkbox', label='♪', name='ignsong', value=true, x=2, y=4}, 41 | {class='checkbox', label='#', name='ignwell', value=true, x=3, y=4} 42 | } 43 | local buttons = {'Run', 'Quit'} 44 | 45 | local meta,styles=karaskel.collect_head(subtitle,false) 46 | local pressed, result = aegisub.dialog.display(dialog_config,buttons) 47 | if pressed~="Run" then aegisub.cancel() end 48 | 49 | local zhoList, engList = {},{} 50 | local ii = 1 51 | while ii<=#subtitle do 52 | if ii==selected[1] then ii = selected[#selected] 53 | elseif subtitle[ii].class~='dialogue' then 54 | else 55 | local line = subtitle[ii] 56 | local linetext = line.text:gsub('^ *',''):gsub(' *$','') 57 | local linetextstriptag = linetext:gsub('{[^}]*}','') 58 | if line.comment==true then 59 | elseif (result.ignstar==true and linetextstriptag:match('^.')=='*') 60 | or (result.ignsong==true and linetextstriptag:match('^...')=='♪') 61 | or (result.ignwell==true and linetextstriptag:match('^.')=='#') then 62 | line.text = '{\\an8}'..linetext 63 | subtitle[ii] = line 64 | else 65 | line.text = linetext 66 | subtitle[ii] = line 67 | if result.sel=='Chinese Lines' then table.insert(engList, ii) 68 | else table.insert(zhoList, ii) end 69 | end 70 | end 71 | ii = ii + 1 72 | end 73 | for _,i in ipairs(selected) do 74 | if subtitle[i].class~='dialogue' then 75 | else 76 | local line = subtitle[i] 77 | local linetext = line.text:gsub('^ *',''):gsub(' *$','') 78 | local linetextstriptag = linetext:gsub('{[^}]*}','') 79 | if line.comment==true then 80 | elseif (result.ignstar==true and linetextstriptag:match('^.')=='*') 81 | or (result.ignsong==true and linetextstriptag:match('^...')=='♪') 82 | or (result.ignwell==true and linetextstriptag:match('^.')=='#') then 83 | line.text = '{\\an8}'..linetext 84 | subtitle[i] = line 85 | else 86 | line.text = linetext 87 | subtitle[i] = line 88 | if result.sel=='Chinese Lines' then table.insert(zhoList, i) 89 | else table.insert(engList, i) end 90 | end 91 | end 92 | end 93 | 94 | local zhoH, zhoT, engH, engT = 1,1,1,1 95 | while zhoH<=#zhoList and engH<=#engList do 96 | local zhoI ,engI = zhoList[zhoT], engList[engT] 97 | local zhoLine, engLine = subtitle[zhoI], subtitle[engI] 98 | local score = getscore(zhoLine, engLine) 99 | if score<=0 then 100 | if engLine.end_time0 then 132 | if engT+1<=#engList and getscore(subtitle[zhoList[zhoT+1]], subtitle[engList[engT+1]])0 then 142 | if zhoT+1<=#zhoList and getscore(subtitle[zhoList[zhoT+1]], subtitle[engList[engT+1]])0 then 60 | a1 = a:gsub(result.match, function (...) 61 | local b = {...} 62 | return "~"..table.concat(b,",")..",~" 63 | end) 64 | else 65 | a1 = a:gsub(result.match,"~") 66 | end 67 | return "{"..a1.."}" 68 | end) 69 | 70 | for i=1,count_match do 71 | -- get capture args 72 | local capture_table = {} 73 | if count_capture>0 then 74 | local capture_string = linetext:match("~([%d%.%-,]+)~") 75 | for j in capture_string:gmatch("([^,]+),") do 76 | j = tonumber(j) 77 | table.insert(capture_table,j) 78 | end 79 | linetext = linetext:gsub("~([%d%.%-,]+)~","~") 80 | end 81 | -- write rules into the table 82 | local rule_table = {} 83 | for j=1,count_rule do 84 | local rname = "r"..j 85 | local rule_temp = result[rname] 86 | -- head & tail () 87 | if rule_temp:match("^%(")==nil then 88 | rule_temp = rule_temp:gsub("^","(") 89 | rule_temp = rule_temp:gsub("$",")") 90 | end 91 | -- value: ldur,sstart,send,sdur 92 | rule_temp = rule_temp:gsub("ldur",ldur) 93 | rule_temp = rule_temp:gsub("sstart",ldur/count_match*(i-1)) 94 | rule_temp = rule_temp:gsub("send",ldur/count_match*(i)) 95 | rule_temp = rule_temp:gsub("sdur",ldur/count_match) 96 | rule_temp = rule_temp:gsub("sindex",i) 97 | rule_temp = rule_temp:gsub("scount",count_match) 98 | rule_temp = rule_temp:gsub("lindex",si) 99 | rule_temp = rule_temp:gsub("lcount",#selected) 100 | -- capture: cap%d 101 | rule_temp = rule_temp:gsub("cap(%d)",function (a) 102 | a = tonumber(a) 103 | return capture_table[a] 104 | end) 105 | -- rule calculation 106 | while rule_temp:match("%(")~=nil do 107 | rule_temp = rule_temp:gsub("([a-z]*%([^%(%)]*%))",function (a) return calculation(a) end) 108 | end 109 | rule_temp = tonumber(rule_temp) 110 | table.insert(rule_table,rule_temp) 111 | end 112 | 113 | -- write rules into the str 114 | local str_temp = result.str 115 | for j=1,count_rule do 116 | local judge_str_pre,judge_str_post = str_temp:match("(.-)\\d"),str_temp:match("\\d(.+)") 117 | if (judge_str_pre:match("\\t%($")~=nil and judge_str_post:match("^,\\d")~=nil) or 118 | (judge_str_pre:match("\\move%([%d%.%-]+,[%d%.%-]+,[%d%.%-]+,[%d%.%-]+,$") and judge_str_post:match("^,\\d")~=nil) then 119 | rule_table[j],rule_table[j+1] = math.min(rule_table[j],rule_table[j+1]),math.max(rule_table[j],rule_table[j+1]) 120 | end 121 | str_temp = str_temp:gsub("\\d",rule_table[j],1) 122 | end 123 | 124 | -- rewrite the sub with the str 125 | linetext = linetext:gsub("({[^}]*)".."~".."([^}]*})", function (h,t) 126 | return h..str_temp..t 127 | end, 1) 128 | end 129 | 130 | line.text=linetext 131 | subtitle[li]=line 132 | aegisub.progress.set((si-1)/#selected*100) 133 | end 134 | 135 | :: loop_end :: 136 | aegisub.set_undo_point(script_name) 137 | return selected 138 | end 139 | 140 | -- str: [a-z]*%([^%(%)]*%) 141 | function calculation(str) 142 | local pre,strin = str:match("([a-z]*)%(([^%)]*)%)") 143 | if pre~=nil and pre~="" then 144 | if pre=="random" then 145 | local a,b = strin:match("([%d%.%-]+),([%d%.%-]+)") 146 | a,b = tonumber(a),tonumber(b) 147 | a,b = math.min(a,b),math.max(a,b) 148 | return math.random(a,b) 149 | end 150 | else 151 | local pa,a,x,pb,b = strin:match("(%-?)([%d%.]+)([%+%-%*/])(%-?)([%d%.]+)") 152 | if x~=nil and x~="" then 153 | a,b = tonumber(a),tonumber(b) 154 | if pa=="-" then a = -1*a end 155 | if pb=="-" then b = -1*b end 156 | if x=="+" then return a+b 157 | elseif x=="-" then return a-b 158 | elseif x=="*" then return a*b 159 | elseif x=="/" and b~=0 then return a/b 160 | else 161 | aegisub.log("math error") 162 | aegisub.cancel() 163 | end 164 | else 165 | -- only one number 166 | strin = tonumber(strin) 167 | return strin 168 | end 169 | end 170 | end 171 | 172 | function macro_validation(subtitle, selected, active) 173 | return true 174 | end 175 | 176 | --This is what puts your automation in Aegisub's automation list 177 | aegisub.register_macro(script_name,script_description,main,macro_validation) -------------------------------------------------------------------------------- /src/C Translation.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | README: 3 | 4 | Translation 5 | 6 | goto my repository https://github.com/zhang-changwei/Automation-scripts-for-Aegisub for the latest version 7 | 8 | Feature: 9 | Translate the values of the tags 10 | which means add values with equivalent inteval (or specific function relationship) to the tags of selected lines 11 | Now \pos \fscx \fscy \[i]clip tags are supported 12 | 13 | Manual: 14 | 1. Select the lines 15 | 2. Check the tag(s) you want to translate on the GUI and set the corresponding three values 16 | start: the translation value of the first line 17 | end: the translation value of the last line 18 | accel: the gradient function (default value 1 means equivalent interval) 19 | 3. Press OK and run 20 | 21 | Bug Report: 22 | 1. Only zero or one \pos tag can be included in one line 23 | 24 | Updated on 9 Fre 2021 25 | Add "index" to accurately recognize the tag 26 | Add more options: frz,fsp,fsvp,frx,fry,fax,fay 27 | Bug Fixed 28 | 29 | Updated on 21 Jan 2021 30 | Bug of position recognition fixed 31 | Name changed to Translation 32 | 33 | Updated on 20 Jan 2021 34 | New feature (scale|clip) added 35 | 36 | Updated on 7 Dec 2020 37 | ]] 38 | 39 | script_name="C Translation" 40 | script_description="Trasnlation v3.2.1" 41 | script_author="chaaaaang" 42 | script_version="3.2.1" 43 | 44 | include("karaskel.lua") 45 | 46 | --GUI 47 | local dialog_config={ 48 | {class="checkbox",name="set",label="setting",value=true,x=0,y=0,hint="true: time mode, false: line mode"}, 49 | {class="label",label="Translation",x=2,y=0},--start 50 | {class="label",label="Translation",x=4,y=0},--end 51 | {class="label",label="Smooth",x=6,y=0},--divation 52 | {class="label",label="Translation / Smooth",x=8,y=0},--accel 53 | {class="label",label="Smooth",x=10,y=0},--transverse 54 | {class="label",label="Translation / Smooth",x=12,y=0},--index 55 | --posx 56 | {class="checkbox",name="posx",label="posx",value=false,x=0,y=1}, 57 | {class="label",label="posx_start",x=1,y=1}, 58 | {class="floatedit",name="posx_start",value=0,x=2,y=1}, 59 | {class="label",label="posx_end",x=3,y=1}, 60 | {class="floatedit",name="posx_end",value=0,x=4,y=1}, 61 | {class="label",label="deviation",x=5,y=1}, 62 | {class="floatedit",name="posx_deviation",value=0,x=6,y=1}, 63 | {class="label",label="accel",x=7,y=1}, 64 | {class="floatedit",name="posx_accel",value=1,x=8,y=1}, 65 | {class="label",label="transverse",x=9,y=1}, 66 | {class="floatedit",name="posx_transverse",value=1,x=10,y=1}, 67 | --posy 68 | {class="checkbox",name="posy",label="posy",value=false,x=0,y=2}, 69 | {class="label",label="posy_start",x=1,y=2}, 70 | {class="floatedit",name="posy_start",value=0,x=2,y=2}, 71 | {class="label",label="posy_end",x=3,y=2}, 72 | {class="floatedit",name="posy_end",value=0,x=4,y=2}, 73 | {class="label",label="deviation",x=5,y=2}, 74 | {class="floatedit",name="posy_deviation",value=0,x=6,y=2}, 75 | {class="label",label="accel",x=7,y=2}, 76 | {class="floatedit",name="posy_accel",value=1,x=8,y=2}, 77 | {class="label",label="transverse",x=9,y=2}, 78 | {class="floatedit",name="posy_transverse",value=1,x=10,y=2}, 79 | --fscx 80 | {class="checkbox",name="fscx",label="fscx",value=false,x=0,y=3}, 81 | {class="label",label="fscx_start",x=1,y=3}, 82 | {class="floatedit",name="fscx_start",value=0,x=2,y=3}, 83 | {class="label",label="fscx_end",x=3,y=3}, 84 | {class="floatedit",name="fscx_end",value=0,x=4,y=3}, 85 | {class="label",label="deviation",x=5,y=3}, 86 | {class="floatedit",name="fscx_deviation",value=0,x=6,y=3}, 87 | {class="label",label="accel",x=7,y=3}, 88 | {class="floatedit",name="fscx_accel",value=1,x=8,y=3}, 89 | {class="label",label="transverse",x=9,y=3}, 90 | {class="floatedit",name="fscx_transverse",value=1,x=10,y=3}, 91 | {class="label",label="index",x=11,y=3}, 92 | {class="intedit",name="fscx_index",value=1,x=12,y=3}, 93 | --fscy 94 | {class="checkbox",name="fscy",label="fscy",value=false,x=0,y=4}, 95 | {class="label",label="fscy_start",x=1,y=4}, 96 | {class="floatedit",name="fscy_start",value=0,x=2,y=4}, 97 | {class="label",label="fscy_end",x=3,y=4}, 98 | {class="floatedit",name="fscy_end",value=0,x=4,y=4}, 99 | {class="label",label="deviation",x=5,y=4}, 100 | {class="floatedit",name="fscy_deviation",value=0,x=6,y=4}, 101 | {class="label",label="accel",x=7,y=4}, 102 | {class="floatedit",name="fscy_accel",value=1,x=8,y=4}, 103 | {class="label",label="transverse",x=9,y=4}, 104 | {class="floatedit",name="fscy_transverse",value=1,x=10,y=4}, 105 | {class="label",label="index",x=11,y=4}, 106 | {class="intedit",name="fscy_index",value=1,x=12,y=4}, 107 | --frz 108 | {class="checkbox",name="frz",label="frz",value=false,x=0,y=5}, 109 | {class="label",label="frz_start",x=1,y=5}, 110 | {class="floatedit",name="frz_start",value=0,x=2,y=5}, 111 | {class="label",label="frz_end",x=3,y=5}, 112 | {class="floatedit",name="frz_end",value=0,x=4,y=5}, 113 | {class="label",label="deviation",x=5,y=5}, 114 | {class="floatedit",name="frz_deviation",value=0,x=6,y=5}, 115 | {class="label",label="accel",x=7,y=5}, 116 | {class="floatedit",name="frz_accel",value=1,x=8,y=5}, 117 | {class="label",label="transverse",x=9,y=5}, 118 | {class="floatedit",name="frz_transverse",value=1,x=10,y=5}, 119 | {class="label",label="index",x=11,y=5}, 120 | {class="intedit",name="frz_index",value=1,x=12,y=5}, 121 | --clip_x 122 | {class="checkbox",name="clip_x",label="clip_x",value=false,x=0,y=6}, 123 | {class="label",label="clip_x_start",x=1,y=6}, 124 | {class="floatedit",name="clip_x_start",value=0,x=2,y=6}, 125 | {class="label",label="clip_x_end",x=3,y=6}, 126 | {class="floatedit",name="clip_x_end",value=0,x=4,y=6}, 127 | {class="label",label="deviation",x=5,y=6}, 128 | {class="floatedit",name="clip_x_deviation",value=0,x=6,y=6}, 129 | {class="label",label="accel",x=7,y=6}, 130 | {class="floatedit",name="clip_x_accel",value=1,x=8,y=6}, 131 | {class="label",label="transverse",x=9,y=6}, 132 | {class="floatedit",name="clip_x_transverse",value=1,x=10,y=6}, 133 | --clip_y 134 | {class="checkbox",name="clip_y",label="clip_y",value=false,x=0,y=7}, 135 | {class="label",label="clip_y_start",x=1,y=7}, 136 | {class="floatedit",name="clip_y_start",value=0,x=2,y=7}, 137 | {class="label",label="clip_y_end",x=3,y=7}, 138 | {class="floatedit",name="clip_y_end",value=0,x=4,y=7}, 139 | {class="label",label="deviation",x=5,y=7}, 140 | {class="floatedit",name="clip_y_deviation",value=0,x=6,y=7}, 141 | {class="label",label="accel",x=7,y=7}, 142 | {class="floatedit",name="clip_y_accel",value=1,x=8,y=7}, 143 | {class="label",label="transverse",x=9,y=7}, 144 | {class="floatedit",name="clip_y_transverse",value=1,x=10,y=7}, 145 | --other 146 | {class="checkbox",name="other_button",label="other tags",value=false,x=0,y=8}, 147 | {class="dropdown",name="other",items={"fsp","fsvp","fax","fay","frx","fry"},x=0,y=9}, 148 | {class="label",label="other_start",x=1,y=9}, 149 | {class="floatedit",name="other_start",value=0,x=2,y=9}, 150 | {class="label",label="other_end",x=3,y=9}, 151 | {class="floatedit",name="other_end",value=0,x=4,y=9}, 152 | {class="label",label="deviation",x=5,y=9}, 153 | {class="floatedit",name="other_deviation",value=0,x=6,y=9}, 154 | {class="label",label="accel",x=7,y=9}, 155 | {class="floatedit",name="other_accel",value=1,x=8,y=9}, 156 | {class="label",label="transverse",x=9,y=9}, 157 | {class="floatedit",name="other_transverse",value=1,x=10,y=9}, 158 | {class="label",label="index",x=11,y=9}, 159 | {class="intedit",name="other_index",value=1,x=12,y=9}, 160 | --multiply 161 | {class="checkbox",name="XeqY",label="fscy<-fscx",value=false,x=2,y=10}, 162 | {class="checkbox",name="multiply",label="multiply",width=2,value=false,x=3,y=10}, 163 | --note 164 | {class="label",x=0,y=10,width=2,label="Translation v3.2.1"}, 165 | {class="label",x=0,y=11,width=13,label="better to be used in FRAME BY FRAME lines, may use the linetofbf in Relocator first, index argument Z+"}, 166 | {class="label",x=0,y=12,width=13,label="index: index of the tag you would like to translate in ALL this tag"}, 167 | {class="label",x=0,y=13,width=13,label="ATTENTION: positive posy means moving downwards"}, 168 | {class="label",x=0,y=14,width=13,label="Translation function: (tail-head)*x^a+head Smooth function: ((1-cos(x^t))/2)^a*deviation"}, 169 | {class="label",x=0,y=15,width=13,label="accel: arg (0,inf) SMOOTH the peak get sharper as the argument increases"}, 170 | {class="label",x=0,y=16,width=13,label="transverse: arg (0,inf) transverse deviation from center, the peak move from left to right as the argument increases"} 171 | } 172 | local buttons={"Translation","Smooth","Quit"} 173 | 174 | function main(subtitle, selected, active) 175 | local meta,styles=karaskel.collect_head(subtitle,false) 176 | local xres, yres, ar, artype = aegisub.video_size() 177 | 178 | --count the size N,T 179 | local start_f,end_f = 0,0 180 | 181 | for sa,la in ipairs(selected) do 182 | local line = subtitle[la] 183 | if (sa == 1) then start_f = aegisub.frame_from_ms(line.start_time) end 184 | end_f = aegisub.frame_from_ms(line.start_time) 185 | end 186 | local T = end_f - start_f + 1 187 | local N = #selected 188 | 189 | local pressed, result = aegisub.dialog.display(dialog_config,buttons) 190 | if (pressed=="Quit") then aegisub.cancel() end 191 | --all false 192 | if (result["posx"]==false and result["posy"]==false and result["fscx"]==false and result["fscy"]==false and result["clip_x"]==false and result["clip_y"]==false and result["frz"]==false and result["other_button"]==false) then 193 | aegisub.cancel() 194 | else 195 | if result.XeqY==true then 196 | result.fscy = true 197 | result.fscy_start, result.fscy_end, result.fscy_accel, result.fscy_deviation, result.fscy_transverse, result.fscy_index 198 | = result.fscx_start, result.fscx_end, result.fscx_accel, result.fscx_deviation, result.fscx_transverse, result.fscx_index 199 | end 200 | --loop begins 201 | local i = 0 202 | for si,li in ipairs(selected) do 203 | i = i + 1 204 | local line=subtitle[li] 205 | local now_f = aegisub.frame_from_ms(line.start_time) 206 | local t = now_f - start_f + 1 207 | karaskel.preproc_line(subtitle,meta,styles,line) 208 | --preprocession 209 | local linetext = (line.text:match("^{")==nil) and "{}"..line.text or line.text 210 | linetext = linetext:gsub("}{","") 211 | 212 | --posx posy 213 | if (result["posx"]==true or result["posy"]==true) then 214 | --confirm the \pos is in the tag 215 | if (linetext:match("^{[^}]*\\pos[^}]*}")==nil) then 216 | if (linetext:match("\\an%d")==nil) then 217 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",line.x,line.y)) 218 | elseif (linetext:match("\\an1")~=nil) then 219 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",line.styleref.margin_l,yres-line.styleref.margin_b)) 220 | elseif (linetext:match("\\an2")~=nil) then 221 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",xres/2,yres-line.styleref.margin_b)) 222 | elseif (linetext:match("\\an3")~=nil) then 223 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",xres-line.styleref.margin_r,yres-line.styleref.margin_b)) 224 | elseif (linetext:match("\\an4")~=nil) then 225 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",line.styleref.margin_l,yres/2)) 226 | elseif (linetext:match("\\an5")~=nil) then 227 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",xres/2,yres/2)) 228 | elseif (linetext:match("\\an6")~=nil) then 229 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",xres-line.styleref.margin_r,yres/2)) 230 | elseif (linetext:match("\\an7")~=nil) then 231 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",line.styleref.margin_l,line.styleref.margin_t)) 232 | elseif (linetext:match("\\an8")~=nil) then 233 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",xres/2,line.styleref.margin_t)) 234 | elseif (linetext:match("\\an9")~=nil) then 235 | linetext=linetext:gsub("^{",string.format("{\\pos(%.3f,%.3f)",xres-line.styleref.margin_r,line.styleref.margin_t)) 236 | else 237 | end 238 | end 239 | 240 | if (result["posx"]==true) then 241 | local gposx = linetext:match("\\pos%([^,]*") 242 | gposx = tonumber(gposx:sub(6)) 243 | linetext=linetext:gsub("\\pos%([^,]*,",string.format("\\pos(%.3f,", gposx + interpolate(result["posx_start"],result["posx_end"],result["posx_accel"],N,T,i,t,result["set"],result["posx_deviation"],result["posx_transverse"],pressed))) 244 | end 245 | if (result["posy"]==true) then 246 | local gpx, gpy = linetext:match("\\pos%(([^,]*),([^%)]*)%)") 247 | gpy = tonumber(gpy)+interpolate(result["posy_start"],result["posy_end"],result["posy_accel"],N,T,i,t,result["set"],result["posy_deviation"],result["posy_transverse"],pressed) 248 | linetext=linetext:gsub("\\pos%(([^,]*),[^%)]*%)",string.format("\\pos(%s,%.3f)",gpx,gpy)) 249 | end 250 | end 251 | --fscx 252 | if (result["fscx"]==true) then 253 | if (linetext:match("\\fscx")==nil) then 254 | linetext=linetext:gsub("^{",string.format("{\\fscx%.2f",line.styleref.scale_x)) 255 | end 256 | local deviation = interpolate(result["fscx_start"],result["fscx_end"],result["fscx_accel"],N,T,i,t,result["set"],result["fscx_deviation"],result["fscx_transverse"],pressed) 257 | linetext = translation(linetext,"\\fscx",deviation,result["fscx_index"],"([^}]*)}\\fscx([%d%.%-]+)","\\fscx[%d%.%-]+$",result["multiply"]) 258 | end 259 | --fscy 260 | if (result["fscy"]==true) then 261 | if (linetext:match("\\fscy")==nil) then 262 | linetext=linetext:gsub("^{",string.format("{\\fscy%.2f",line.styleref.scale_y)) 263 | end 264 | local deviation = interpolate(result["fscy_start"],result["fscy_end"],result["fscy_accel"],N,T,i,t,result["set"],result["fscy_deviation"],result["fscy_transverse"],pressed) 265 | linetext = translation(linetext,"\\fscy",deviation,result["fscy_index"],"([^}]*)}\\fscy([%d%.%-]+)","\\fscy[%d%.%-]+$",result["multiply"]) 266 | end 267 | --frz 268 | if (result["frz"]==true) then 269 | if (linetext:match("\\frz")==nil) then 270 | linetext=linetext:gsub("^({[^}]*)}",function (a) return string.format("%s\\frz%.2f}",a,line.styleref.angle) end) 271 | end 272 | local deviation = interpolate(result["frz_start"],result["frz_end"],result["frz_accel"],N,T,i,t,result["set"],result["frz_deviation"],result["frz_transverse"],pressed) 273 | linetext = translation(linetext,"\\frz",deviation,result["frz_index"],"([^}]*)}\\frz([%d%.%-]+)","\\frz[%d%.%-]+$") 274 | end 275 | --clip 276 | if (result["clip_x"]==true or result["clip_y"]==true) then 277 | linetext = linetext:gsub("(\\[i]?clip)([^%)]+)%)", 278 | function(c,d) 279 | --odd or even xyxy 280 | local o_e=0 281 | local trs_clip = c 282 | for head,num in d:gmatch("([^%d%.%-]+)([%d%.%-]+)") do 283 | if (o_e == 0 and result["clip_x"]==true) then 284 | trs_clip = string.format("%s%s%.2f",trs_clip,head,num+interpolate(result["clip_x_start"],result["clip_x_end"],result["clip_x_accel"],N,T,i,t,result["set"],result["clip_x_deviation"],result["clip_x_transverse"],pressed)) 285 | elseif (o_e == 1 and result["clip_y"]==true) then 286 | trs_clip = string.format("%s%s%.2f",trs_clip,head,num+interpolate(result["clip_y_start"],result["clip_y_end"],result["clip_y_accel"],N,T,i,t,result["set"],result["clip_y_deviation"],result["clip_y_transverse"],pressed)) 287 | else 288 | trs_clip = string.format("%s%s%.2f",trs_clip,head,num) 289 | end 290 | o_e = (o_e + 1)%2 291 | end 292 | return trs_clip..")" 293 | end) 294 | end 295 | --other tags 296 | if (result["other_button"]==true) then 297 | --fsp 298 | if (result["other"]=="fsp") then 299 | if (linetext:match("\\fsp")==nil) then 300 | linetext=linetext:gsub("^({[^}]*)}",function (a) return string.format("%s\\fsp%.2f}",a,line.styleref.spacing) end) 301 | end 302 | local deviation = interpolate(result["other_start"],result["other_end"],result["other_accel"],N,T,i,t,result["set"],result["other_deviation"],result["other_transverse"],pressed) 303 | linetext = translation(linetext,"\\fsp",deviation,result["other_index"],"([^}]*)}\\fsp([%d%.%-]+)","\\fsp[%d%.%-]+$") 304 | --fsvp 305 | elseif (result["other"]=="fsvp") then 306 | if (linetext:match("\\fsvp")==nil) then 307 | linetext=linetext:gsub("^({[^}]*)}",function (a) return a.."\\fsvp0}" end) 308 | end 309 | local deviation = interpolate(result["other_start"],result["other_end"],result["other_accel"],N,T,i,t,result["set"],result["other_deviation"],result["other_transverse"],pressed) 310 | linetext = translation(linetext,"\\fsvp",deviation,result["other_index"],"([^}]*)}\\fsvp([%d%.%-]+)","\\fsvp[%d%.%-]+$") 311 | --fax 312 | elseif (result["other"]=="fax") then 313 | if (linetext:match("\\fax")==nil) then 314 | linetext=linetext:gsub("^({[^}]*)}",function (a) return a.."\\fax0}" end) 315 | end 316 | local deviation = interpolate(result["other_start"],result["other_end"],result["other_accel"],N,T,i,t,result["set"],result["other_deviation"],result["other_transverse"],pressed) 317 | linetext = translation(linetext,"\\fax",deviation,result["other_index"],"([^}]*)}\\fax([%d%.%-]+)","\\fax[%d%.%-]+$") 318 | --fay 319 | elseif (result["other"]=="fay") then 320 | if (linetext:match("\\fay")==nil) then 321 | linetext=linetext:gsub("^({[^}]*)}",function (a) return a.."\\fay0}" end) 322 | end 323 | local deviation = interpolate(result["other_start"],result["other_end"],result["other_accel"],N,T,i,t,result["set"],result["other_deviation"],result["other_transverse"],pressed) 324 | linetext = translation(linetext,"\\fay",deviation,result["other_index"],"([^}]*)}\\fay([%d%.%-]+)","\\fay[%d%.%-]+$") 325 | --frx 326 | elseif (result["other"]=="frx") then 327 | if (linetext:match("\\frx")==nil) then 328 | linetext=linetext:gsub("^({[^}]*)}",function (a) return a.."\\frx0}" end) 329 | end 330 | local deviation = interpolate(result["other_start"],result["other_end"],result["other_accel"],N,T,i,t,result["set"],result["other_deviation"],result["other_transverse"],pressed) 331 | linetext = translation(linetext,"\\frx",deviation,result["other_index"],"([^}]*)}\\frx([%d%.%-]+)","\\frx[%d%.%-]+$") 332 | --fry 333 | elseif (result["other"]=="fry") then 334 | if (linetext:match("\\fry")==nil) then 335 | linetext=linetext:gsub("^({[^}]*)}",function (a) return a.."\\fry0}" end) 336 | end 337 | local deviation = interpolate(result["other_start"],result["other_end"],result["other_accel"],N,T,i,t,result["set"],result["other_deviation"],result["other_transverse"],pressed) 338 | linetext = translation(linetext,"\\fry",deviation,result["other_index"],"([^}]*)}\\fry([%d%.%-]+)","\\fry[%d%.%-]+$") 339 | end 340 | end 341 | --more feature (gradient|smooth) coming 342 | line.text = linetext 343 | line.actor = "C" 344 | subtitle[li] = line 345 | end 346 | --loop ends 347 | end 348 | aegisub.set_undo_point(script_name) 349 | return selected 350 | end 351 | 352 | function interpolate(head,tail,accel,N,T,i,t,judge,deviation,transverse,button) 353 | -- i 1-N 354 | local bias, x 355 | if (button=="Translation") then 356 | if (judge==false) then 357 | bias = (1/(N-1)*(i-1))^accel 358 | else 359 | bias = (1/(T-1)*(t-1))^accel 360 | end 361 | return (tail-head)*bias+head 362 | elseif (button=="Smooth") then 363 | if (judge==false) then 364 | -- ((1-cos(x^t))/2)^a*deviation 365 | x = 2*(i-1)* math.pi/(N-1) 366 | else 367 | x = 2*(t-1)* math.pi/(T-1) 368 | end 369 | bias = ((1-math.cos(x^transverse))/2)^accel*deviation 370 | return bias 371 | else 372 | return 0 373 | end 374 | 375 | end 376 | 377 | function translation(linetext,tagtype,deviation,index,match,matchtail,multiply) 378 | local tt_table={} 379 | for tg,tx in linetext:gmatch("({[^}]*})([^{]*)") do 380 | table.insert(tt_table,{tag=tg,text=tx}) 381 | end 382 | 383 | local rebuild = "" 384 | local count = 0 385 | for _,tt in ipairs(tt_table) do 386 | if (tt.tag:match(tagtype)==nil) then 387 | rebuild = rebuild..tt.tag..tt.text 388 | else 389 | tt.tag = tt.tag:gsub(tagtype,"}"..tagtype) 390 | tt.tag = tt.tag..tagtype.."0" 391 | local rebuild_tag = "" 392 | 393 | for p,q in tt.tag:gmatch(match) do 394 | count = count + 1 395 | if (count == index) then 396 | if multiply~=true then 397 | rebuild_tag = string.format("%s%s%s%.2f",rebuild_tag,p,tagtype,q+deviation) 398 | else 399 | rebuild_tag = string.format("%s%s%s%.2f",rebuild_tag,p,tagtype,q*deviation) 400 | end 401 | else 402 | rebuild_tag = rebuild_tag..p..tagtype..q 403 | end 404 | end 405 | rebuild_tag = rebuild_tag:gsub(matchtail,"}") 406 | rebuild = rebuild..rebuild_tag..tt.text 407 | count = count - 1 408 | end 409 | end 410 | return rebuild 411 | end 412 | 413 | --This optional function lets you prevent the user from running the macro on bad input 414 | function macro_validation(subtitle, selected, active) 415 | --Check if the user has selected valid lines 416 | --If so, return true. Otherwise, return false 417 | return true 418 | end 419 | 420 | --This is what puts your automation in Aegisub's automation list 421 | aegisub.register_macro(script_name,script_description,main,macro_validation) --------------------------------------------------------------------------------