├── docs ├── assets │ ├── timer.png │ ├── wave.png │ ├── ass2svg.mp4 │ ├── kfx-code.png │ ├── svg2ass.png │ ├── timer2.png │ ├── timer3.png │ ├── abacusGUI.png │ ├── edittags1.png │ ├── edittags10.png │ ├── edittags11.png │ ├── edittags2.png │ ├── edittags3.png │ ├── edittags4.png │ ├── edittags5.png │ ├── edittags6.png │ ├── edittags7.png │ ├── edittags8.png │ ├── edittags9.png │ ├── kfx-button.png │ ├── kfx-effect.png │ ├── wobble-gui.png │ ├── abacusbuttons.png │ ├── add-grain-GUI.png │ ├── extrapolate.png │ ├── kfx-modifier.png │ ├── qcreport-main.png │ ├── AegiGUI_check_1.png │ ├── AegiGUI_color_1.png │ ├── AegiGUI_edit_1.png │ ├── AegiGUI_edit_2.png │ ├── AegiGUI_label_1.png │ ├── AegiGUI_label_2.png │ ├── abacusinputbox.png │ ├── add-grain-menu.png │ ├── fold-copy-paste.mp4 │ ├── foldoperations1.png │ ├── foldoperations2.png │ ├── foldoperations3.png │ ├── foldoperations4.png │ ├── kfx-linemarker.png │ ├── vectorGradient.mp4 │ ├── wobble-animate.png │ ├── wobble-example.png │ ├── AegiGUI_dropdown_1.png │ ├── AegiGUI_dropdown_2.png │ ├── AegiGUI_intedit_1.png │ ├── AegiGUI_showcase_1.png │ ├── AegiGUI_showcase_2.png │ ├── AegiGUI_showcase_3.png │ ├── AegiGUI_textbox_1.png │ ├── AegiGUI_textbox_2.png │ ├── AegiGUI_textbox_3.png │ ├── auto-fade-tracking.mp4 │ ├── extrapolate-circle.mp4 │ ├── extrapolate-linear.mp4 │ ├── fold-with-comments.png │ ├── qcreport-generate.png │ ├── rotated-gradient.png │ ├── wobble-oscillate.png │ ├── AegiGUI_coloralpha_1.png │ ├── AegiGUI_floatedit_1.png │ ├── rotated-gradient-gui.png │ ├── vector-gradient-rect.png │ ├── vector-gradient-text.png │ ├── extrapolate-non-linear.mp4 │ ├── fold-without-comments.png │ ├── chromatic-abberation-gui.png │ ├── fold-without-comments-old.png │ ├── chromatic-abberation-shape.mp4 │ ├── chromatic-abberation-simple.mp4 │ ├── chromatic-abberation-keepcolor.mp4 │ ├── vector-gradient-rect-withoutBlur.png │ └── vector-gradient-text-without-blur.png ├── Wave.md ├── Fit Text in Clip.md ├── ASS2SVG.md ├── index.md ├── svg2ass.md ├── examples │ ├── transform.moon │ ├── sections.moon │ └── AegiGUI.moon ├── Vector Gradient.md ├── Rotated Gradient.md ├── Quality Report.md ├── Chromatic Abberation.md ├── Bidirectional Snapping.md ├── Auto Fade.md ├── KFX.md ├── Remove Tags.md ├── Extrapolate Tracking.md ├── Wobble.md ├── Add Grain.md ├── Fold Operations.md ├── Timing Assistant.md └── Abacus.md ├── misc ├── Grain Font │ ├── grain.ttf │ ├── Grain-Black.ttf │ ├── Grain-Bold.ttf │ ├── Grain-Medium.ttf │ ├── Grain-SemiBold.ttf │ └── Grain-ExtraBold.ttf ├── prepare-commit-msg └── phos.ink2ass.py ├── .gitignore ├── .github └── workflows │ ├── mkdocs.yml │ └── depctrl.yml ├── mkdocs.yml ├── README.md ├── macros ├── phos.snap.lua ├── phos.ASS2SVG.moon ├── phos.FitTextInClip.moon ├── phos.wave.lua ├── phos.img2ass.moon ├── phos.CombineDrawings.moon ├── phos.AddGrain.moon ├── phos.AutoGradient.moon ├── phos.qcreport.moon ├── phos.RemoveTags.moon ├── phos.svg2ass.moon ├── phos.VectorGradient.moon ├── phos.AutoFade.moon ├── phos.ChromaticAbberation.moon └── phos.RotateGradient.moon ├── test └── phos.EditTags.ass └── source └── phos.RemoveTags.norg /docs/assets/timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/timer.png -------------------------------------------------------------------------------- /docs/assets/wave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/wave.png -------------------------------------------------------------------------------- /docs/assets/ass2svg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/ass2svg.mp4 -------------------------------------------------------------------------------- /docs/assets/kfx-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/kfx-code.png -------------------------------------------------------------------------------- /docs/assets/svg2ass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/svg2ass.png -------------------------------------------------------------------------------- /docs/assets/timer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/timer2.png -------------------------------------------------------------------------------- /docs/assets/timer3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/timer3.png -------------------------------------------------------------------------------- /docs/assets/abacusGUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/abacusGUI.png -------------------------------------------------------------------------------- /docs/assets/edittags1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags1.png -------------------------------------------------------------------------------- /docs/assets/edittags10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags10.png -------------------------------------------------------------------------------- /docs/assets/edittags11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags11.png -------------------------------------------------------------------------------- /docs/assets/edittags2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags2.png -------------------------------------------------------------------------------- /docs/assets/edittags3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags3.png -------------------------------------------------------------------------------- /docs/assets/edittags4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags4.png -------------------------------------------------------------------------------- /docs/assets/edittags5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags5.png -------------------------------------------------------------------------------- /docs/assets/edittags6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags6.png -------------------------------------------------------------------------------- /docs/assets/edittags7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags7.png -------------------------------------------------------------------------------- /docs/assets/edittags8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags8.png -------------------------------------------------------------------------------- /docs/assets/edittags9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/edittags9.png -------------------------------------------------------------------------------- /docs/assets/kfx-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/kfx-button.png -------------------------------------------------------------------------------- /docs/assets/kfx-effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/kfx-effect.png -------------------------------------------------------------------------------- /docs/assets/wobble-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/wobble-gui.png -------------------------------------------------------------------------------- /misc/Grain Font/grain.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/misc/Grain Font/grain.ttf -------------------------------------------------------------------------------- /docs/assets/abacusbuttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/abacusbuttons.png -------------------------------------------------------------------------------- /docs/assets/add-grain-GUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/add-grain-GUI.png -------------------------------------------------------------------------------- /docs/assets/extrapolate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/extrapolate.png -------------------------------------------------------------------------------- /docs/assets/kfx-modifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/kfx-modifier.png -------------------------------------------------------------------------------- /docs/assets/qcreport-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/qcreport-main.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_check_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_check_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_color_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_color_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_edit_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_edit_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_edit_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_edit_2.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_label_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_label_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_label_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_label_2.png -------------------------------------------------------------------------------- /docs/assets/abacusinputbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/abacusinputbox.png -------------------------------------------------------------------------------- /docs/assets/add-grain-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/add-grain-menu.png -------------------------------------------------------------------------------- /docs/assets/fold-copy-paste.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/fold-copy-paste.mp4 -------------------------------------------------------------------------------- /docs/assets/foldoperations1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/foldoperations1.png -------------------------------------------------------------------------------- /docs/assets/foldoperations2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/foldoperations2.png -------------------------------------------------------------------------------- /docs/assets/foldoperations3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/foldoperations3.png -------------------------------------------------------------------------------- /docs/assets/foldoperations4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/foldoperations4.png -------------------------------------------------------------------------------- /docs/assets/kfx-linemarker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/kfx-linemarker.png -------------------------------------------------------------------------------- /docs/assets/vectorGradient.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/vectorGradient.mp4 -------------------------------------------------------------------------------- /docs/assets/wobble-animate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/wobble-animate.png -------------------------------------------------------------------------------- /docs/assets/wobble-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/wobble-example.png -------------------------------------------------------------------------------- /misc/Grain Font/Grain-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/misc/Grain Font/Grain-Black.ttf -------------------------------------------------------------------------------- /misc/Grain Font/Grain-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/misc/Grain Font/Grain-Bold.ttf -------------------------------------------------------------------------------- /docs/assets/AegiGUI_dropdown_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_dropdown_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_dropdown_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_dropdown_2.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_intedit_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_intedit_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_showcase_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_showcase_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_showcase_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_showcase_2.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_showcase_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_showcase_3.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_textbox_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_textbox_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_textbox_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_textbox_2.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_textbox_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_textbox_3.png -------------------------------------------------------------------------------- /docs/assets/auto-fade-tracking.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/auto-fade-tracking.mp4 -------------------------------------------------------------------------------- /docs/assets/extrapolate-circle.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/extrapolate-circle.mp4 -------------------------------------------------------------------------------- /docs/assets/extrapolate-linear.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/extrapolate-linear.mp4 -------------------------------------------------------------------------------- /docs/assets/fold-with-comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/fold-with-comments.png -------------------------------------------------------------------------------- /docs/assets/qcreport-generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/qcreport-generate.png -------------------------------------------------------------------------------- /docs/assets/rotated-gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/rotated-gradient.png -------------------------------------------------------------------------------- /docs/assets/wobble-oscillate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/wobble-oscillate.png -------------------------------------------------------------------------------- /misc/Grain Font/Grain-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/misc/Grain Font/Grain-Medium.ttf -------------------------------------------------------------------------------- /misc/Grain Font/Grain-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/misc/Grain Font/Grain-SemiBold.ttf -------------------------------------------------------------------------------- /docs/assets/AegiGUI_coloralpha_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_coloralpha_1.png -------------------------------------------------------------------------------- /docs/assets/AegiGUI_floatedit_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/AegiGUI_floatedit_1.png -------------------------------------------------------------------------------- /docs/assets/rotated-gradient-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/rotated-gradient-gui.png -------------------------------------------------------------------------------- /docs/assets/vector-gradient-rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/vector-gradient-rect.png -------------------------------------------------------------------------------- /docs/assets/vector-gradient-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/vector-gradient-text.png -------------------------------------------------------------------------------- /misc/Grain Font/Grain-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/misc/Grain Font/Grain-ExtraBold.ttf -------------------------------------------------------------------------------- /docs/assets/extrapolate-non-linear.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/extrapolate-non-linear.mp4 -------------------------------------------------------------------------------- /docs/assets/fold-without-comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/fold-without-comments.png -------------------------------------------------------------------------------- /docs/assets/chromatic-abberation-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/chromatic-abberation-gui.png -------------------------------------------------------------------------------- /docs/assets/fold-without-comments-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/fold-without-comments-old.png -------------------------------------------------------------------------------- /docs/assets/chromatic-abberation-shape.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/chromatic-abberation-shape.mp4 -------------------------------------------------------------------------------- /docs/assets/chromatic-abberation-simple.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/chromatic-abberation-simple.mp4 -------------------------------------------------------------------------------- /docs/assets/chromatic-abberation-keepcolor.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/chromatic-abberation-keepcolor.mp4 -------------------------------------------------------------------------------- /docs/assets/vector-gradient-rect-withoutBlur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/vector-gradient-rect-withoutBlur.png -------------------------------------------------------------------------------- /docs/assets/vector-gradient-text-without-blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/HEAD/docs/assets/vector-gradient-text-without-blur.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*/ 3 | !.git* 4 | !.github/** 5 | !macros/*.lua 6 | !macros/*.moon 7 | !modules/phos/*.moon 8 | !DependencyControl.json 9 | !/assets/* 10 | !test/*.ass 11 | !docs/** 12 | !mkdocs.yml 13 | !source/*.norg 14 | !misc/Grain Font/*.ttf 15 | !misc/phos.ink2ass.py 16 | -------------------------------------------------------------------------------- /misc/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mapfile -t macros < <(git diff-index --cached --name-only 'HEAD^{tree}' | awk -F/ '{print $NF}' | uniq | sed -e "s|.lua$||g" -e "s|^phos.||g" -e "s|.moon$||g" -e "s|.norg$||g" -e 's/^./\U&/g' -e 's/ ./\U&/g' | awk '!a[$0]++') 4 | 5 | if ((${#macros[@]})) && ! head -1 "$1" | grep -q -e '^\[' -e '^fixup!' -e '^squash!'; then 6 | sed -i '1s/^/['"$( 7 | IFS=, 8 | printf "%s" "${macros[*]}" 9 | )"'] /' "$1" 10 | fi 11 | -------------------------------------------------------------------------------- /.github/workflows/mkdocs.yml: -------------------------------------------------------------------------------- 1 | name: mkdocs 2 | on: 3 | push: 4 | paths: 5 | - 'docs/**' 6 | - 'mkdocs.yml' 7 | branches: 8 | - main 9 | permissions: 10 | contents: write 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Python runtime 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: 3.x 21 | 22 | - name: Set up build cache 23 | uses: actions/cache@v4 24 | with: 25 | key: ${{ github.ref }} 26 | path: .cache 27 | 28 | - name: Install Python dependencies 29 | run: | 30 | pip install mkdocs-material 31 | 32 | - name: Deploy documentation 33 | env: 34 | GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }} 35 | run: | 36 | mkdocs gh-deploy --force 37 | -------------------------------------------------------------------------------- /docs/Wave.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.wave.lua) 4 | 5 | ![wave](./assets/wave.png) 6 | 7 | Wave is a script that allows you to mimic the wavy signs through some fscx, fscy and fsp trickery. When you open the scripts, there are few parameters you can change, and you'll have to go through some trial and error to determine what values give you the result you want. 8 | 9 | Credits for the original code excerpt before my modification goes to [The0x539](https://github.com/The0x539) 10 | 11 | Below are few examples of what kind of waves you can achieve but depending on the values you use, you can create different types of waves as well. 12 | 13 | 17 | -------------------------------------------------------------------------------- /docs/Fit Text in Clip.md: -------------------------------------------------------------------------------- 1 | **Not Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.FitTextInClip.moon) 4 | 5 | 9 | 10 | This script will fit the text in the current line inside the rectangular clip and try to make it justified. 11 | 12 | !!! warning 13 | 14 | This script uses [Yutils](https://github.com/TypesettingTools/Yutils) to determine the width of the text. Therefore, the efficacy of this script entirely depends on whether Yutils can accurately determine the width of the text. 15 | 16 | # Usage 17 | 18 | - Add `\an7` to the line. 19 | - Move the line so that the top left corner of text is exactly where it should be. 20 | - Draw a rectangular clip starting very close to top left corner of the text such that the width of the clip is equal to the length of the text you want to be fitted to. 21 | - Run the script. 22 | -------------------------------------------------------------------------------- /docs/ASS2SVG.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.ASS2SVG.moon) 4 | 5 | `ASS2SVG` allows you to convert shape from ass subtitles to svg path that you can open in program like Inkscape and make changes to it. This is a companion script to [inkscape-svg2ass](https://github.com/PhosCity/inkscape-svg2ass), an Inkscape extension that converts svg path to ass shape. 6 | 7 | !!! warning "Warning" 8 | 9 | This script only works in shapes and not text. This is because of differences in the semantics of how text is rendered in SVG that is very different than ASS. This not only makes the sizes of fonts different but there is also no 3D transformation of text in SVG which makes exporting tags like frx and fry impossible. 10 | 11 | # Usage 12 | 13 | 17 | 18 | - Select lines with shapes in Aegisub. 19 | - Run the script. 20 | - Navigate to the folder where you want to save the svg and input the filename of the svg. 21 | - You will find a file exported in that folder with that name. 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Home 2 | 3 | This is the collection of the Aegisub scripts made by me along with detailed explanation for its usage. 4 | 5 | Scripts that I've written can broadly be classified into two types: scripts that are in [Dependency Control](https://github.com/TypesettingTools/DependencyControl) and those that are not. Scripts that are in Dependency Control are those scripts that are ready for broad public usage and those which aren't are either still in development or are not useful enough to be added. 6 | 7 | ## Installing Aegisub Scripts 8 | 9 | The guide to installing both scripts that are available in Dependency Control and those that aren't is explained in this [guide](https://fansubbers.miraheze.org/wiki/Guide:Installing_Aegisub_scripts) 10 | 11 | ## Script/Feature Request and Bug Reporting 12 | 13 | If you want to report bug or ask for a feature to be added in an already existing script or have an idea for a new script that you want to be made, you can reach out in the following places. 14 | 15 | - [Github Issues](https://github.com/PhosCity/Aegisub-Scripts/issues) 16 | - DM me at discord (`PhosCity#4786`) 17 | - I haunt the hallways of GJM fansub discord server pretty regularly. 18 | 19 | I can't assure I'll accept all requests but if it's good enough and possible by my knowledge, I'll try to get it made. 20 | -------------------------------------------------------------------------------- /docs/svg2ass.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.svg2ass.moon) 4 | 5 | The script svg2ass allows you to select an SVG file from Aegisub itself and convert it to shape, clip or iclip. It works in both windows and Unix operating system. I generally create SVG files using GIMP(can perform complex selections and convert those selections to SVG in a matter of seconds) or Inkscape and use svg2ass to convert them to subtitle lines. 6 | 7 | In order to convert svg to ass, you will need to download [a python file](https://github.com/PhosCity/Aegisub-Scripts/blob/main/misc/phos.ink2ass.py) and save it somewhere. You also need ton install python itself if you haven't already. Finally you need to install a python module named _inkex_. You can find a guide to how to install python and python module all over the internet if you don't know already. 8 | 9 | Once you install this script, the first thing you should do in Aegisub is to set the config and provide the path where you have the ink2ass python file is located. To do this, go to Automation -> svg2ass -> Config in Aegisub and select the file in file browser. 10 | 11 | To use this script, simply click on `Import` button and select the SVG file. The resulting lines will have the same start time, end time and style as the selected line. If you checked clip or iclip in the GUI, the resulting shape will be converted to clip or iclip respectively. 12 | 13 | ![svg2ass](./assets/svg2ass.png) 14 | -------------------------------------------------------------------------------- /docs/examples/transform.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Transform Test" 2 | export script_description = "ASSFoundation Test" 3 | export script_version = "0.0.1" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.TransformTest" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https://github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"} 15 | } 16 | } 17 | LineCollection, ASS = depctrl\requireModules! 18 | logger = depctrl\getLogger! 19 | 20 | testFunction = (sub, sel) -> 21 | lines = LineCollection sub, sel 22 | lines\runCallback (lines, line, i) -> 23 | data = ASS\parse line 24 | transforms = data\getTags "transform" 25 | for index, tr in ipairs transforms 26 | logger\log "\n=== Transform #{index} ===" 27 | start_time = tr.startTime\get! 28 | end_time = tr.endTime\get! 29 | accel = tr.accel\get! 30 | logger\log "Start Time: #{start_time}" 31 | logger\log "End Time: #{end_time}" 32 | logger\log "Accel: #{accel}" 33 | for tag in *tr.tags\getTags! 34 | tagname = tag.__tag.name 35 | tagvalue = table.concat({tag\getTagParams!}, ",") 36 | logger\log "#{tagname}(#{tagvalue})" 37 | lines\replaceLines! 38 | 39 | depctrl\registerMacro testFunction 40 | -------------------------------------------------------------------------------- /docs/Vector Gradient.md: -------------------------------------------------------------------------------- 1 | **Not Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.VectorGradient.moon) 4 | 5 | The original idea for using vector shapes to create gradient was shared by [Noroino Hanakao](https://github.com/noroinohanako) . This script only automates this process. 6 | 7 | The main idea is that this script creates shapes that when blurred gives you a gradient. The main benefit of using this over clip gradient is that this can create gradient in one line which makes it easier to motion track and manipulate. 8 | 9 | # Usage 10 | 11 | 15 | 16 | | Name | Number of clip points | Gradient type | 17 | | ----- | --------------------- | ------------- | 18 | | Wedge | 3 | Linear | 19 | | Ring | 2 | Radial | 20 | | Star | 2 | Radial | 21 | 22 | - Create the vectorial clip with the required number of clips. For wedge, create the clip in clockwise direction. 23 | 24 | - Run the script and choose which type of gradient you want. 25 | 26 | # Examples 27 | 28 | Example 1: 29 | 30 | === "Rectangular mask gradient" 31 | 32 | ![image](./assets/vector-gradient-rect.png) 33 | 34 | === "Same mask without blur" 35 | 36 | ![image](./assets/vector-gradient-rect-withoutBlur.png) 37 | 38 | Example 2: 39 | 40 | === "Text gradient" 41 | 42 | ![image](./assets/vector-gradient-text.png) 43 | 44 | === "Same text without blur" 45 | 46 | ![image](./assets/vector-gradient-text-without-blur.png) 47 | -------------------------------------------------------------------------------- /docs/Rotated Gradient.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.RotateGradient.moon) 4 | 5 | ![image](./assets/rotated-gradient.png) 6 | 7 | ![image](./assets/rotated-gradient-gui.png){: style="height:233px;width:344px"} 8 | 9 | `Rotated Gradient` allows you to create a gradient at an angle using clips. Traditionally, to create a rotated gradient, you'd have to create a small strip of shapes. That would make it impossible to edit the sign afterwards. 10 | 11 | # Usage 12 | 13 | 17 | 18 | - Ready the text or shape which you want to gradient. 19 | 20 | - Duplicate the line and change tags in the duplicated line. 21 | 22 | !!! info 23 | 24 | You can duplicate it twice to have 2 stop gradient. This basically means duplicate the line `n` times to create `n` stop gradient 25 | 26 | - Draw 3 point vectorial clip in clockwise direction in any of the line. The first two points determines the direction of gradient and the last point determines the distance upto which the gradient must continue. 27 | 28 | - Run the script. In the GUI, select the pixel for each line, accel and tick all the tags you want to gradient. 29 | 30 | !!! warning 31 | 32 | If your text has border and shadow, you must split the lines into layers with and without border and then depending on if you want to gradient fill or border and shadow, run the script in that layer. Otherwise, you may see strips in you line. This is not a limitation of this script. This is limitation of ASS rendering. 33 | -------------------------------------------------------------------------------- /docs/examples/sections.moon: -------------------------------------------------------------------------------- 1 | export script_name = "ASSFoundation Test" 2 | export script_description = "ASSFoundation Test" 3 | export script_version = "0.0.1" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.ASSFoundationTest" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https://github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"} 15 | } 16 | } 17 | LineCollection, ASS = depctrl\requireModules! 18 | logger = depctrl\getLogger! 19 | 20 | testFunction = (sub, sel) -> 21 | lines = LineCollection sub, sel 22 | lines\runCallback (lines, line, i) -> 23 | data = ASS\parse line 24 | data\callback (section) -> 25 | if section.class == ASS.Section.Tag 26 | logger\log "\n=====ASS.Section.Tag=====" 27 | for tag in *section\getTags! 28 | tagname = tag.__tag.name 29 | tagvalue = table.concat({tag\getTagParams!}, ",") 30 | logger\log "#{tagname}(#{tagvalue})" 31 | elseif section.class == ASS.Section.Text 32 | logger\log "\n=====ASS.Section.Text=====" 33 | logger\log section.value 34 | elseif section.class == ASS.Section.Comment 35 | logger\log "\n=====ASS.Section.Comment=====" 36 | logger\log section.value 37 | elseif section.class == ASS.Section.Drawing 38 | logger\log "\n=====ASS.Section.Drawing=====" 39 | logger\dump section\getString! 40 | lines\replaceLines! 41 | 42 | depctrl\registerMacro testFunction 43 | -------------------------------------------------------------------------------- /docs/Quality Report.md: -------------------------------------------------------------------------------- 1 | **Not Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.qcreport.moon) 4 | 5 | ![image](./assets/qcreport-main.png) 6 | 7 | This script is designed to write and generate QC reports from Aegisub. If you wish to write QC reports using a media player, there is a program called [mpvQC](https://github.com/mpvqc/mpvQC) which is designed to do just that. 8 | 9 | The top row of the GUI consists of configurable sections like timing, typesetting etc. that you can tick to specify the type of note. If nothing is ticked, it's treated as a general note. Below that is a drop-down which has pre-made reports for each sections for making even faster notes. 10 | 11 | Below that is a textbox where you are free to write you report. The way you format your report in the textbox is preserved. If you selected any pre-made reports, it is appended to the beginning of the text in the text-box. You can leave this box empty and only select the pre-made report. 12 | 13 | Finally, there is a checkbox called `Use video frame`. Normally, the report is added to current line but if you tick this, the report is added on the basis of the current video frame. If the current video frame has a subtitle, then the report is added to that line. If there isn't, then an empty line with report is inserted whose time is same as the video frame. 14 | 15 | After you write all your notes, you can generate a report and a properly formatted note will with time will be generated that you can copy and share. The generated report is fully compatible with arch1t3cht's [Note Browser](https://github.com/arch1t3cht/Aegisub-Scripts#note-browser) script. After you generate the report, and you no longer need them in your subtitle, you can clean them up too. 16 | 17 | ![image](./assets/qcreport-generate.png) 18 | 19 | There is also a config where you can configure a lot of things about the script so be sure to check that out. 20 | -------------------------------------------------------------------------------- /docs/Chromatic Abberation.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Source to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/source/phos.ChromaticAbberation.norg) 4 | 5 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.ChromaticAbberation.moon) 6 | 7 | Chromatic Aberration is a stylistic effect that mimics the failure of a lens to properly focus all colors. The script `Chromatic Abberation` as the name suggests allows you to emulate that effect in your sign. 8 | 9 | !!! info "Info" 10 | 11 | The script is still new and the result might not be perfect. Don't hesiatate to report bugs and suggestions. 12 | 13 | # Usage 14 | 15 | ![image](./assets/chromatic-abberation-gui.png){: style="height:139px;width:305px"} 16 | 17 | x Offset and y Offset allows you to increase the size of the effect. 18 | 19 | If you tick the checkbox `Keep Original Color`, the script will apply the effect but will not change the base color. If this is unticked, the base color will be whatever is the result of subtractive color mixing. 20 | 21 | The three colors will allow user to change the colors of the effect. There is no need to change it most of the time since this is the default colors of chromatic abberation but if the user wants to change it, they can. 22 | 23 | ## Simple Example 24 | 25 | 29 | 30 | ## Keep Original Color of the line 31 | 32 | 36 | 37 | ## Works similarly with shape 38 | 39 | 43 | -------------------------------------------------------------------------------- /docs/Bidirectional Snapping.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.snap.lua) 4 | 5 | !!! info "Info" 6 | 7 | If you use my `Timing Assistant` script, there is no need to use this script anymore. 8 | 9 | This script is made to be hotkeyed to allow you to snap to start or end of the the line to frame before or ahead respectively while timing. While there are other scripts that allow you snap to keyframes, they are either a simple snap to adjacent keyframes or a TPP style snapping scripts. 10 | 11 | While timing, you might have come across the case multiple times when you go to the next line only to realize that the end time of the line overshoots the keyframe you want to snap to. Then you have no choice but to snap backwards using mouse. Here's where this script comes handy. This script is made to be hotkeyed, so first hotkey the end snapping and the start snapping function in the audio section. When you press the hotkey to snap end once, it snaps to the keyframe ahead. If you press the same hotkey again, it snaps to the keyframe behind. Then every press of the hotkey will continue snapping to previous keyframe. This way, you can snap to the keyframe ahead or behind using the same hotkey. For the start time, the opposite happens. One press snaps behind, and then double press snaps forward. 12 | 13 | I use Bidirectional Snapping in combination with [this script](https://github.com/The0x539/Aegisub-Scripts/blob/trunk/src/0x.JoinPrevious.lua) which is also hotkeyed and allows me to link the previous line to current line without moving to previous line. 14 | 15 | An attempt has been made below to showcase its usage, but you should use it yourself to see how it works. Here, `w` has been hotkeyed to end-snapping and `q` has been hotkeyed to beginning-snapping. 16 | 17 | 21 | -------------------------------------------------------------------------------- /docs/Auto Fade.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.AutoFade.moon) 4 | 5 | `Auto Fade` allows you to determine the fade in and fade out time of a sign. Manually determining them requires you to step through frames of the video and find the frame where fade in ends or fade out starts and then add the fade tag to the line. This script automates all of those steps. 6 | 7 | !!! warning "Warning" 8 | 9 | This script only works in arch1t3cht's [Aegisub](https://github.com/arch1t3cht/Aegisub). 10 | 11 | # Usage 12 | 13 | ## Static Sign 14 | 15 | 19 | 20 | - Time your sign. 21 | - Determine if your sign has fade in, fade out or both. 22 | - Play the video until you reach any frame in which there is neither fade in nor fade out. 23 | - Now you have two options. Either add a single point `clip/iclip` over the sign as shown in first example of the video or hover over the Japanese sign, right click and choose "Copy coordinates to Clipboard" as shown in the second example of video above. 24 | - Open the script (while staying in the same video frame). The co-ordinate should have automatically be picked up and shown in the GUI. Then choose `Fade in` or `Fade out` or `Both` button depending on what you want. 25 | - The script will automatically add appropriate fade to your text. 26 | 27 | ## Moving Sign 28 | 29 | 33 | 34 | If your sign is moving and you also have motion tracking data for the sign, Available, you can use that data to determine the fade for such moving sign as well. 35 | 36 | All the steps are the same as shown above except before you click the button, paste the tracking data in the text box and change the drop-down from `Single Co-ordinate` to `Tracking Data`. 37 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: PhosCity's Aegisub Script 2 | site_author: PhosCity 3 | 4 | repo_name: PhosCity/Aegisub-Scripts 5 | repo_url: https://github.com/PhosCity/Aegisub-Scripts 6 | 7 | nav: 8 | - Home: index.md 9 | - Typesetting: 10 | - Abacus: Abacus.md 11 | - Add Grain: Add Grain.md 12 | - ASS2SVG: ASS2SVG.md 13 | - Auto Fade: Auto Fade.md 14 | - Chromatic Abberation: Chromatic Abberation.md 15 | - Edit Tags: Edit Tags.md 16 | - Extrapolate Tracking: Extrapolate Tracking.md 17 | - Fold Operations: Fold Operations.md 18 | - Remove Tags: Remove Tags.md 19 | - Rotated Gradient: Rotated Gradient.md 20 | - svg2ass: svg2ass.md 21 | - Wave: Wave.md 22 | - Wobble: Wobble.md 23 | - Fit Text in Clip: Fit Text in Clip.md 24 | - Vector Gradient: Vector Gradient.md 25 | - Timing: 26 | - Timing Assistant: Timing Assistant.md 27 | - Bidirectional Snapping: Bidirectional Snapping.md 28 | - Karaoke: 29 | - KFX: KFX.md 30 | - QC: 31 | - Quality Report: Quality Report.md 32 | - Module: 33 | - AegiGUI: AegiGUI.md 34 | - AssfPlus: AssfPlus.md 35 | - Others: 36 | - ASSFoundation: ASSFoundation.md 37 | 38 | theme: 39 | name: material 40 | features: 41 | - navigation.tabs 42 | - navigation.tabs.sticky 43 | - navigation.sections 44 | - toc.integrate 45 | - navigation.top 46 | - search.suggest 47 | - search.highlight 48 | - content.tabs.link 49 | - content.code.annotation 50 | - content.code.copy 51 | language: en 52 | palette: 53 | # Palette toggle for dark mode 54 | - scheme: slate 55 | toggle: 56 | icon: material/toggle-switch 57 | name: Switch to light mode 58 | # Palette toggle for light mode 59 | - scheme: default 60 | toggle: 61 | icon: material/toggle-switch-off-outline 62 | name: Switch to dark mode 63 | 64 | markdown_extensions: 65 | - attr_list 66 | - admonition 67 | - pymdownx.details 68 | - pymdownx.tabbed: 69 | alternate_style: true 70 | - pymdownx.highlight: 71 | anchor_linenums: true 72 | - pymdownx.superfences 73 | 74 | extra: 75 | analytics: 76 | provider: google 77 | property: !ENV GOOGLE_ANALYTICS_KEY 78 | -------------------------------------------------------------------------------- /docs/KFX.md: -------------------------------------------------------------------------------- 1 | **Not Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.kfx.moon) 4 | 5 | This script is designed to write or modify karaoke template lines for [The0x539's KaraTemplater](https://github.com/The0x539/Aegisub-Scripts/blob/trunk/src/0x.KaraTemplater.moon) 6 | 7 | 12 | 13 | #### Buttons 14 | 15 | ![image](./assets/kfx-button.png) 16 | 17 | - Next: Proceed to next step 18 | - Insert: Insert a new line above the current selected line 19 | - Replace: Replace the current selected line 20 | - Modify: Modify the template in the current selected line 21 | 22 | #### First Window - Line Marker 23 | 24 | ![image](./assets/kfx-linemarker.png) 25 | 26 | The first window allows you to select line markers. You won't be able to proceed unless you select a line marker. 27 | 28 | #### Second Window - Modifiers 29 | 30 | ![image](./assets/kfx-modifier.png) 31 | 32 | The second window allows you to choose modifiers. This is not compulsory, and you can proceed without choosing anything. For modifiers that need extra arguments, you can type them in the textbox. The options are dynamic i.e. only the modifiers available for the chosen line markers are available for choosing. 33 | 34 | #### Third Window - Effect 35 | 36 | ![image](./assets/kfx-code.png) 37 | 38 | The third window allows you to write the actual effects. If you chose `code` line marker, a text box will appear where you can write your code. Variables can be written one variable per line. Function can be written as you write in your IDE. Indentation is purely visual and not necessary, but you can indent with tabs or spaces if you wish. 39 | 40 | ![image](./assets/kfx-effect.png) 41 | 42 | For any other line marker, a HYDRA type GUI will appear. Every box is a textbox so that you can write value, variables or function. If you don't find any option for what you want to write, the `Additional` option is for you. 43 | 44 | If you tick transform, this same GUI will be reloaded for you to write tags inside the transform section. 45 | 46 | In the bottom, there are boxes for the effect, actor and text. While these are for giving you information of current state of template line, you can edit it manually, and it will be saved. 47 | -------------------------------------------------------------------------------- /docs/Remove Tags.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.RemoveTags.moon) 4 | 5 | ![remove_tags](https://user-images.githubusercontent.com/65547311/211794907-5974c7cf-a824-4dd4-a96c-56a268ac7cc9.png) 6 | 7 | This script deals with all things related to removing tags from the line. One of the main motivation for writing this script when a script like unanimated's `Script Cleanup` exists is because I would spend a lot of time searching the exact tag I wanted to remove from the 40 options of the GUI. When I have only 10 tags, I wanted to choose the tags I want to remove from those 10 tags only. So, GUI of this script is dynamically generated i.e. only tags that are available in the selected lines are available for you to remove. The GUI from the image above is not what you'll see when you run it. 8 | 9 | #### `Remove All` button 10 | 11 | - If you simply click the `Remove All` button, it removes all the tags form the selected lines. 12 | - If you check `Start Tags` in the top row and then press `Remove All` button, it removes all start tags from selected lines. 13 | - Similarly, checking `Inline Tags` in top row removes all inline tags. 14 | 15 | #### `Remove Tags` button 16 | 17 | - All the tags that you individually tick would be removed. 18 | - If `Start tags` is checked, the selected tags will only be removed from start tags. 19 | - If `Inline tags` is checked, the selected tags will only be removed from inline tags. 20 | - If `Transform` is checked, the selected tags will only be removed from transforms. 21 | - If `Inverse` is checked, all the tags except the selected ones will be deleted. 22 | 23 | #### `Remove Group` button 24 | 25 | This button executes the things you select in the left column and is mostly used to delete groups of tags at once. Staying true to it's mission, the script also dynamically creates this section. Which means that if your selection does not contain any color tags for example, the option to remove color tags won't be available. You can also tick `Start Tag` or `Inline Tag` the top row and only remove the tag group from start tag block or inline tag block only. The groups available are: 26 | 27 | - All color tags (c, 1c, 2c, 3c, 4c) 28 | - All alpha tags (alpha, 1a, 2a, 3a, 4a) 29 | - All rotation tags (frz, frx, fry) 30 | - All scale tags (fs, fscx, fscy) 31 | - All perspective tags (frz, frx, fry, fax, fay, org) 32 | - All inline tags except last (useful for undoing gradient) 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/Extrapolate Tracking.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.ExtrapolateTracking.moon) 4 | 5 | When you're motion-tracking a sign, and you cannot track the first or last few frames either because the sign moved out of the screen or it faded out, you can use this script to extrapolate the tracking for those lines. 6 | 7 | What this script does: 8 | 9 | - Gathers position, scale and rotation data from selected lines 10 | - Uses k-fold cross validation to find a proper degree of polynomial to fit the data (and some other fuckery when proper degree cannot be found.) 11 | - Uses that degree to extrapolate the data 12 | - Generates motion tracking data from the extrapolated data 13 | - Uses Aegisub Motion internally to apply that motion tracking data 14 | 15 | # Screenshot 16 | 17 | ![image](./assets/extrapolate.png){: style="height:180px;width:355px"} 18 | 19 | # Usage 20 | 21 | !!! warning "Requirements" 22 | 23 | - All selected lines must be 1 frame long. 24 | - Selection must be consecutive and sorted by time. 25 | - If lines are split in layers, run the script separately for each layer. 26 | 27 | - Try your best to track using Mocha or Blender. It's better to get proper track for as many frames as you can. 28 | - Apply the motion tracking data to the line. Delete the badly tracked lines in the beginning or end if necessary. 29 | - Select all the lines and run the script. 30 | - Choose if you want to extrapolate at start or end using the drop-down in GUI. 31 | - Enter how many additional frames you want to extrapolate. 32 | - Click on `Apply` button. 33 | 34 | # Examples 35 | 36 | !!! info 37 | 38 | The graph generated in the video is for demonstration and debuggin purposes only. It'll not pop up everytime you use script. 39 | 40 | ## Extrapolation of linear track 41 | 42 | 46 | 47 | ## Extrapolation of non-linear track 48 | 49 | 53 | 54 | ## Extrapolation of circular track 55 | 56 | !!! warning 57 | 58 | In some cases like in the example shown below, when the line rotates about a single origin point, you should disable `Origin` in the gui for proper extrapolation. 59 | 60 | 64 | -------------------------------------------------------------------------------- /docs/Wobble.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.wobble.moon) 4 | 5 | Wobble is a remake of a very old script that distorts the text with the parameters you choose. The old script however was very difficult to use. It neither recognized the text already present in the line nor the tags used nor did it output the result in a usable format. So I decided to remake it and add other features. 6 | 7 | ![wobble](./assets/wobble-gui.png) 8 | 9 | When you open the script, you will be presented with the GUI as shown above. Imagine that when you use this script, it distorts the text along a wave horizontally or vertically or both. The parameters control the nature of the wave thus controls how the text is distorted. Frequency is the number of waves (crest and trough) you want in the text in percentage. You'll most likely use 20-40% for optimum results. Strength is the number of pixels that the text will be distorted to. With trial and error, you can create many interesting distortions. 10 | 11 | As you can see below, the top is the original font and everything below it is distorted using this script. I use it when the sign is distorted or I need an irregular font that I cannot find. 12 | 13 | ![wobble2](./assets/wobble-example.png) 14 | 15 | ![image](./assets/wobble-animate.png) 16 | 17 | #### Animate the distortion 18 | 19 | If you want to animate the distortion, first split the line to frames. You can use something like [petzku's script](https://github.com/petzku/Aegisub-Scripts/blob/master/macros/petzku.SplitTimer.moon) to do so. You can control the speed of animation by controlling the number of frames each line has. The more frames per line, the slower the animation. After splitting the line to frames, you need to put the starting value and ending value. I recommend you figure this out beforehand by using main GUI. After you put the starting and ending value for all the required elements, click on `Animate` and the script will handle the rest. 20 | 21 |
22 | Click here to see example animations 23 | 24 | 28 | 29 | 33 | 34 |
35 | 36 | ![wobble-wave](https://user-images.githubusercontent.com/65547311/194462106-f89fa783-8e11-4498-b4d8-0e22b633ed52.png) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhosCity's Aegisub Scripts Collection 2 | 3 | This is the collection of the Aegisub scripts written by me. For detailed explanation of all the scripts and their usage, go to [website](https://phoscity.github.io/Aegisub-Scripts/). 4 | 5 | ## Typesetting 6 | 7 | ### [Abacus](https://phoscity.github.io/Aegisub-Scripts/Abacus/) 8 | 9 | Recalculates values of tags. 10 | 11 | ### [Add Grain](https://phoscity.github.io/Aegisub-Scripts/Add%20Grain/) 12 | 13 | Add static and dynamic grain 14 | 15 | ### [ASS2SVG](https://phoscity.github.io/Aegisub-Scripts/ASS2SVG/) 16 | 17 | Export ass shapes to svg path 18 | 19 | ### [Auto Fade](https://phoscity.github.io/Aegisub-Scripts/Auto%20Fade/) 20 | 21 | Automatically determine fade in and fade out 22 | 23 | ### [Chromatic Abberation](https://phoscity.github.io/Aegisub-Scripts/Chromatic%20Abberation/) 24 | 25 | Add chromatic abberation to shape and text. 26 | 27 | ### [Edit Tags](https://phoscity.github.io/Aegisub-Scripts/Edit%20Tags/) 28 | 29 | Edit tags of current lines 30 | 31 | ### [Extrapolate Tracking](https://phoscity.github.io/Aegisub-Scripts/Extrapolate%20Tracking/) 32 | 33 | Extrapolate the tag values where mocha can't reach 34 | 35 | ### [Fold Operations](https://phoscity.github.io/Aegisub-Scripts/Fold%20Operations/) 36 | 37 | Different operations on folds 38 | 39 | ### [Fit Text in Clip](https://phoscity.github.io/Aegisub-Scripts/Fit%20Text%20in%20Clip/) 40 | 41 | Fit the text inside the rectangular clip 42 | 43 | ### [Remove tags](https://phoscity.github.io/Aegisub-Scripts/Remove%20Tags/) 44 | 45 | Dynamically remove tags based on selection 46 | 47 | ### [Rotated Gradient](https://phoscity.github.io/Aegisub-Scripts/Rotated%20Gradient/) 48 | 49 | Create rotated gradient with clip. 50 | 51 | ### [svg2ass](https://phoscity.github.io/Aegisub-Scripts/svg2ass/) 52 | 53 | Script that uses svg2ass to convert svg files to ass lines 54 | 55 | ### [Vector Gradient](https://phoscity.github.io/Aegisub-Scripts/Vector%20Gradient/) 56 | 57 | Magic triangles + blur gradients 58 | 59 | ### [Wave](https://phoscity.github.io/Aegisub-Scripts/Wave/) 60 | 61 | Make the string wavy 62 | 63 | ### [Wobble](https://phoscity.github.io/Aegisub-Scripts/Wobble/) 64 | 65 | Adds wobbling to text and shape 66 | 67 | ## Timing 68 | 69 | ### [Timing Assistant](https://phoscity.github.io/Aegisub-Scripts/Timing%20Assistant/) 70 | 71 | A second brain for timers. 72 | 73 | ### [Bidirectional Snapping](https://phoscity.github.io/Aegisub-Scripts/Bidirectional%20Snapping/) 74 | 75 | Snap to close keyframes during timing. 76 | 77 | ## Karaoke 78 | 79 | ### [KFX](https://phoscity.github.io/Aegisub-Scripts/KFX/) 80 | 81 | 0x Template Assistant 82 | 83 | ## Quality Checking 84 | 85 | ### [QC Report](https://phoscity.github.io/Aegisub-Scripts/Quality%20Report/) 86 | 87 | Write and generate QC reports 88 | -------------------------------------------------------------------------------- /macros/phos.snap.lua: -------------------------------------------------------------------------------- 1 | -- SCRIPT PROPERTIES 2 | script_name = "Bidirectional Snapping" 3 | script_description = "Snap to close keyframes during timing." 4 | script_author = "PhosCity" 5 | script_version = "1.0.3" 6 | script_namespace = "phos.snap" 7 | 8 | local haveDepCtrl, DependencyControl, depRec = pcall(require, "l0.DependencyControl") 9 | if haveDepCtrl then 10 | depRec = DependencyControl({ 11 | feed = "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 12 | }) 13 | end 14 | 15 | -- HELPER FUNCTIONS 16 | local function get_frame(time) 17 | return aegisub.frame_from_ms(time) 18 | end 19 | 20 | local function get_time(frame) 21 | return aegisub.ms_from_frame(frame) 22 | end 23 | 24 | -- MAIN FUNCTIONS 25 | local function snap_start(subs, sel) 26 | local keyframes = aegisub.keyframes() 27 | for _, i in ipairs(sel) do 28 | if subs[i].class == "dialogue" then 29 | local line = subs[i] 30 | local start_new = nil 31 | local start_kf = get_frame(line.start_time) 32 | local end_kf = get_frame(line.end_time) 33 | for k, kf in ipairs(keyframes) do 34 | if kf < start_kf then 35 | start_new = kf 36 | elseif kf == start_kf and keyframes[k + 1] < end_kf then 37 | start_new = keyframes[k + 1] 38 | end 39 | end 40 | line.start_time = get_time(start_new) 41 | subs[i] = line 42 | end 43 | end 44 | end 45 | 46 | local function snap_end(subs, sel) 47 | local keyframes = aegisub.keyframes() 48 | for _, i in ipairs(sel) do 49 | if subs[i].class == "dialogue" then 50 | local line = subs[i] 51 | local end_new = nil 52 | local start_kf = get_frame(line.start_time) 53 | local end_kf = get_frame(line.end_time) 54 | for k, kf in ipairs(keyframes) do 55 | if kf > end_kf and end_new == nil then 56 | end_new = kf 57 | elseif kf == end_kf and keyframes[k - 1] > start_kf then 58 | end_new = keyframes[k - 1] 59 | end 60 | end 61 | line.end_time = get_time(end_new) 62 | subs[i] = line 63 | end 64 | end 65 | end 66 | 67 | local function snap_both(subs, sel) 68 | snap_start(subs, sel) 69 | snap_end(subs, sel) 70 | end 71 | 72 | --Register macro 73 | if haveDepCtrl then 74 | depRec:registerMacros({ 75 | { "Snap both", "Snap both to keyframes", snap_both }, 76 | { "Snap start", "Snap start to keyframes", snap_start }, 77 | { "Snap end", "Snap end to keyframes", snap_end }, 78 | }) 79 | else 80 | aegisub.register_macro( 81 | script_author .. "/" .. script_name .. "/Snap both to keyframes", 82 | script_description, 83 | snap_both 84 | ) 85 | aegisub.register_macro( 86 | script_author .. "/" .. script_name .. "/Snap start to keyframe", 87 | script_description, 88 | snap_start 89 | ) 90 | aegisub.register_macro(script_author .. "/" .. script_name .. "/Snap end to keyframe", script_description, snap_end) 91 | end 92 | -------------------------------------------------------------------------------- /docs/Add Grain.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.AddGrain.moon) 4 | 5 | The script `Add Grain` allows you to emulate the grain texture in your sign. This script closely follows the manual approach of creating grain from this [guide](). All you need to do before using this script is to install the grain font from this [link](https://github.com/PhosCity/Aegisub-Scripts/tree/main/misc/Grain%20Font) 6 | 7 | 8 | # Usage 9 | 10 | ![image](./assets/add-grain-menu.png){: style="height:216px;width:480px"} 11 | 12 | You can run this script from the menu as well as from the GUI. `Add grain` from the menu adds simple grain while `Add dense grain` makes the grain a little denser using [shad trick](https://fansubbers.miraheze.org/wiki/Guide:Typesetting/Frequently_asked_questions#What_is_the_shad_trick?). If you want even denser grain than that, you need to use the GUI. 13 | 14 | ![image](./assets/add-grain-GUI.png) 15 | 16 | In the GUI, you can increase the `Grain Intensity` to increase the density of the grain. Normally the script makes the grain in pure white and pure black color as recommended in the guide but in cases where you need custom grain color, you can set it in the GUI as well. 17 | 18 | ## Static Grain 19 | 20 | ![image](https://user-images.githubusercontent.com/65547311/208031273-0014ab3f-dc8d-4e15-96e1-24cd59cbc6c0.png) 21 | 22 | - Cover your sign with `!` as shown in the image above. It is easier to do this if you use `\an7` and break the line using `\N` whenever it goes beyond the sign. You do not need to add or modify any tags like `\fn`, `\bord`, `\shad` etc. The script will do it for you. The only tag you might want to change before adding `!` is `\fsc[xy]`. Higher the value of scale, lighter the grain and vice versa. 23 | 24 | - Run the script and choose one of the two options in the menu or from the GUI. The script will, in order, check if the font `grain.ttf` is installed, replace `!` with random characters, split the line to pure white and pure black layers, set the line border and shadow to 0 and add necessary tags like fontnames, color and alpha. 25 | 26 | - Adjust the alpha, blur and scale until it looks like surrounding grain. 27 | 28 | - Clip the line with grain to the text or shape if required. 29 | 30 | ## Dynamic Grain 31 | 32 | - Cover your sign with `!` as described above for static grain. 33 | 34 | - Clip the line to the text or shape if required. 35 | 36 | - Split the line into frames using scripts like [Hyperdimensional Relocator](https://typesettingtools.github.io/depctrl-browser/macros/ua.Relocator/) or [SplitTimer](https://typesettingtools.github.io/depctrl-browser/macros/petzku.SplitTimer/). In some cases, you can go further and check if the anime is animated on twos or threes and use `SplitTimer` to split line in `n` frames accordingly instead of splitting into lines with 1 frame each. 37 | 38 | - Select all the lines and run the script. The result will be dynamic grain. 39 | -------------------------------------------------------------------------------- /macros/phos.ASS2SVG.moon: -------------------------------------------------------------------------------- 1 | export script_name = "ASS2SVG" 2 | export script_description = "Export ass shapes to svg path" 3 | export script_version = "1.0.2" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.ASS2SVG" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"ILL.ILL", version: "1.5.1", url: "https://github.com/TypesettingTools/ILL-Aegisub-Scripts/" 12 | feed: "https://raw.githubusercontent.com/TypesettingTools/ILL-Aegisub-Scripts/main/DependencyControl.json"}, 13 | } 14 | } 15 | ILL = depctrl\requireModules! 16 | {:Ass, :Line, :Path} = ILL 17 | 18 | 19 | bgrTorgb = (bgrHex) -> 20 | blue, green, red = bgrHex\match "&H(%x%x)(%x%x)(%x%x)&" 21 | "#" .. red .. green .. blue 22 | 23 | 24 | alphaToOpacity = (hexAlpha) -> 25 | hexAlpha = hexAlpha\gsub "[H#&]", "" 26 | alphaDecimal = tonumber hexAlpha, 16 27 | 1 - (alphaDecimal / 255) 28 | 29 | 30 | assShapeTosvgPath = (shape, lineData) -> 31 | path = {} 32 | for i = 1, #shape 33 | path[i] = {} 34 | j, contour, cmd = 2, shape[i], nil 35 | while j <= #contour 36 | prev = contour[j - 1]\round 3 37 | curr = contour[j]\round 3 38 | if curr.id == "b" 39 | c = contour[j + 1]\round decimal 40 | d = contour[j + 2]\round decimal 41 | table.insert path[i], "C #{curr.x} #{curr.y} #{c.x} #{c.y} #{d.x} #{d.y}" 42 | j += 2 43 | else 44 | table.insert path[i], "L #{curr.x} #{curr.y}" 45 | j += 1 46 | path[i] = "M #{contour[1].x} #{contour[1].y} " .. table.concat(path[i], " ") .. " Z" 47 | 48 | path = table.concat path, " " 49 | 50 | {:outline, :color1, :color3, :alpha, :alpha1, :alpha3} = lineData 51 | "" 52 | 53 | 54 | saveToFile = (xmlContent) -> 55 | pathsep = package.config\sub(1, 1) 56 | filename = aegisub.dialog.save("Select SVG file", "", aegisub.decode_path("?script")..pathsep, "Svg files (.svg)|*.svg") 57 | if not filename 58 | aegisub.log "You did not provide the filename. Exiting." 59 | return 60 | 61 | file = io.open(filename, "w") 62 | if file 63 | file\write(xmlContent) 64 | file\close! 65 | else 66 | aegisub.log "Error: Could not open file for writing." 67 | 68 | 69 | main = (sub, sel, act) -> 70 | ass = Ass sub, sel, act 71 | 72 | xmlContent = "" 73 | 74 | for l, s in ass\iterSel! 75 | continue if l.comment 76 | Line.extend ass, l 77 | if l.isShape 78 | Line.callBackExpand ass, l, nil, (line) -> 79 | {x, y} = l.data.pos 80 | newPath = Path line.shape 81 | newPath\move x, y 82 | svgPath = assShapeTosvgPath newPath.path, line.data 83 | xmlContent ..= "\n #{svgPath}" 84 | else 85 | ass\warning s, "Text/Empty line is not exported to svg." 86 | xmlContent ..= "\n" 87 | saveToFile xmlContent 88 | 89 | 90 | depctrl\registerMacro main 91 | -------------------------------------------------------------------------------- /macros/phos.FitTextInClip.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Fit Text in Clip" 2 | export script_description = "Fit the text inside the rectangular clip" 3 | export script_version = "0.0.4" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.FitTextInClip" 6 | 7 | -- Readings 8 | -- https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap 9 | -- https://xxyxyz.org/line-breaking/ 10 | -- https://the-algorithms.com/es/algorithm/text-justification 11 | -- https://leetcode.com/problems/text-justification/solutions/24891/concise-python-solution-10-lines/ 12 | 13 | DependencyControl = require "l0.DependencyControl" 14 | depctrl = DependencyControl{ 15 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 16 | { 17 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 18 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 19 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 20 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 21 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 22 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 23 | } 24 | } 25 | LineCollection, ASS, Functional = depctrl\requireModules! 26 | logger = depctrl\getLogger! 27 | { :string } = Functional 28 | 29 | --- Justify the text 30 | ---@param words table list of words to Justify 31 | ---@param maxWidth number width to limit the length of line 32 | ---@param fontObj table font object created by Yutils given a set of values of tags 33 | ---@return string a text seperated by line breakers 34 | textJustification = (words, maxWidth, fontObj) -> 35 | --- Find the width of the text 36 | ---@param text string text whose width must be calculated 37 | ---@return number? width of the text 38 | textWidth = (text) -> 39 | extents = fontObj.text_extents text 40 | tonumber(extents.width) 41 | 42 | result, currentLine, width = {}, {}, 0 43 | spaceWidth = textWidth(" ") 44 | for word in *words 45 | if width + textWidth(word) > maxWidth 46 | for i = 0, math.floor((maxWidth - width)/spaceWidth) - 1 47 | position = i % ( math.max(1, #currentLine - 1) ) 48 | currentLine[position + 1] ..= " " 49 | table.insert result, table.concat(currentLine, " ") 50 | currentLine, width = {}, 0 51 | table.insert currentLine, word 52 | currText = table.concat(currentLine, " ") 53 | width = textWidth(currText) 54 | table.insert result, table.concat(currentLine, " ") 55 | 56 | return table.concat(result, "\\N") 57 | 58 | 59 | --- Main processing function 60 | ---@param sub table subtitle object 61 | ---@param sel table selected lines 62 | main = (sub, sel) -> 63 | lines = LineCollection sub, sel 64 | return if #lines.lines == 0 65 | 66 | lines\runCallback (_, line) -> 67 | aegisub.cancel! if aegisub.progress.is_cancelled! 68 | data = ASS\parse line 69 | 70 | if data\getSectionCount(ASS.Section.Tag) > 1 or data\getSectionCount(ASS.Section.Drawing) > 0 or data\getSectionCount(ASS.Section.Text) == 0 71 | logger\warn "There must be a single text block in the line. Exiting." 72 | return 73 | 74 | clip = data\getTags "clip_rect" 75 | if #clip == 0 76 | logger\warn "Add a rectangular clip in the line fist!" 77 | return 78 | x1, _, x2, _ = clip[1]\getTagParams! 79 | clipWidth = x2 - x1 80 | 81 | effTags = (data\getEffectiveTags -1, true, true, false).tags 82 | if effTags.align\getTagParams! != 7 83 | logger\warn "Please use \\an7 in the line." 84 | return 85 | 86 | data\callback ((section) -> 87 | fontObj = section\getYutilsFont! 88 | text = section\replace("\\N", " ")\replace("%s+", " ")\getString! 89 | words = string.split text, " " 90 | result = textJustification(words, clipWidth, fontObj) 91 | section\set result 92 | ), ASS.Section.Text 93 | data\removeTags "clip_rect" 94 | data\commit! 95 | lines\replaceLines! 96 | 97 | depctrl\registerMacro main 98 | -------------------------------------------------------------------------------- /docs/Fold Operations.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.FoldOperations.moon) 4 | 5 | If you use [arch1t3cht's Aegisub](https://github.com/arch1t3cht/Aegisub),then it 6 | comes with a feature to visually group and 7 | collapse lines in the subtitle grid called folds. 8 | 9 | This script allows you to perform various operations on such folds. 10 | 11 | !!! info 12 | 13 | If you have only one line selected, this script will function on the fold 14 | surrounding that line. If you select multiple lines, then the script will 15 | operate on all the folds around all the selected lines. So in this way you can 16 | either operate on a single fold or multiple folds. You can operate on any fold 17 | if you select even a single line of that fold. You do not need to open a fold 18 | to operate on it, if it's closed. 19 | 20 | # Screenshots 21 | 22 | Fold Operations Menu: 23 | 24 | ![image](./assets/foldoperations1.png) 25 | 26 | Fold Operations GUI: 27 | 28 | ![image](./assets/foldoperations2.png) 29 | 30 | # Usage 31 | 32 | Before I explain the usage of this script, there are two popular ways I've 33 | noticed people use folds. One is to simply select all the lines and create 34 | folds and another is to add commented lines at the start and end of lines where 35 | the first line has name for the fold. If you use commented lines, go to the GUI 36 | of the script and tick `Comments around fold` under config before doing 37 | anything else. 38 | 39 | === "Fold with comments" 40 | 41 | ![image](./assets/fold-with-comments.png) 42 | 43 | === "Same fold without comments" 44 | 45 | ![image](./assets/fold-without-comments.png) 46 | 47 | !!! tip 48 | 49 | I prefer to use comments around my fold since it is cleaner and 50 | instantly allows me to recognize the signs among the list of folds. This script 51 | has an operation that will allow you to easily create named folds with 52 | comments. 53 | 54 | ## Operations 55 | 56 | ### `Select Fold` 57 | 58 | This selects all the lines in fold. If you want to, for example, run `ASSWipe` 59 | on all lines in a fold, use this to select all the lines and wipe. 60 | 61 | ### `Create Fold Around Selected Lines` 62 | 63 | Select all the lines which you want to add to fold and run this. A GUI will 64 | prompt you to enter name for the fold. The script will then insert commented 65 | lines with name and create a fold with selected lines. 66 | 67 | ![image](./assets/foldoperations4.png) 68 | 69 | If the fold is nested, it will show you by the number of arrows before fold names. 70 | 71 | ![image](./assets/foldoperations3.png) 72 | 73 | ### `Comment Fold` 74 | 75 | This comments all the lines in fold. 76 | If a line was already commented before running this, the script remembers it. 77 | 78 | ### `Uncomment Fold` 79 | 80 | This uncomments all the lines in fold. 81 | If the script remembers that a line was commented before running 82 | `Comment Fold`, it does not uncomment them. 83 | 84 | ### `Toggle Comments in Fold` 85 | 86 | This toggles the comments inside the current fold. 87 | Any commented lines will become uncommented, and vice versa. 88 | If the fold was commented using `Comment Fold`, 89 | the state of the already commented folds is respected. 90 | 91 | ### `Comment or Uncomment Fold` 92 | 93 | Comment the lines of fold if it contains any uncommented lines, 94 | otherwise uncomment it all. 95 | 96 | ### `Delete Fold` 97 | 98 | This deletes all the lines of fold. 99 | 100 | ### `Clear Fold` 101 | 102 | This removes the fold without removing the lines itself. 103 | If you use comments around the fold, it will remove that as well. 104 | 105 | ### `Copy Fold` 106 | 107 | This copies all the lines in fold along with it's fold state 108 | and styles to system clipboard. 109 | 110 | ### `Cut Fold` 111 | 112 | This copies all the lines in fold along with it's fold state 113 | and styles to system clipboard and deletes the fold. 114 | 115 | ### `Paste Fold` 116 | 117 | This pastes all the lines in that was copied or cut using this script. 118 | The fold copied from one Aegisub window can be pasted in the same or 119 | different Aegisub window. 120 | 121 | !!! info 122 | 123 | If the file in another Aegisub window does not have styles of copied lines, 124 | those styles will also be added to new file. 125 | 126 | 130 | -------------------------------------------------------------------------------- /docs/Timing Assistant.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Link to 4 | script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.TimingAssistant.moon) 5 | 6 | When I time, I always make a series of decision for every line. Do I need to 7 | add lead in, lead out, snap to keyframes or link the lines? So I wanted to 8 | create a script that allows me to do it in a press of a hotkey. You might be 9 | thinking, "Phos, you just made a TPP". I can assure you it's not. The workflow 10 | of using this script is the same as timing without TPP, but only difference is 11 | that the aforementioned decisions is made for you by the script. 12 | 13 | ## Usage 14 | 15 | The first thing to do after you install the script is to assign a hotkey for 16 | the script in Aegisub. When you do add a hotkey, make sure you add it in the 17 | Audio section. 18 | 19 | In order to time a line, you first exact time a line (subtitle starts and ends 20 | exactly with audio) and then press the hotkey. The script will make the 21 | decision for you whether it should add lead in, snap to keyframe, link the 22 | lines together or add lead out. You then move to the next line by pressing `g` 23 | and repeat. _Exact time, hotkey. Exact time, hotkey. That's it._ 24 | 25 | !!! info 26 | 27 | If the end time of your line exceeds the audio of next line, don't fix it. 28 | Go to the next line, exact time it and then press the hotkey. The script will 29 | fix it. It works in this manner because it can only make proper decision of 30 | line linking of current line in context of start time of next line. 31 | 32 | If you want to check exactly what steps the script takes for decision-making, expand the following and let me know if I got something wrong. 33 | 34 | ??? note "Click here to expand" 35 | 36 | For start time: 37 | 38 | 1. If start time is already snapped to keyframe, it does not make any changes to the start time. 39 | 1. Checks if there is a keyframe within the time specified in the config and snaps to it. 40 | 1. If it was not snapped, it checks the end time of previous line. If it is within the linking time specified in config and not snapped to keyframe, it adds lead in to current line and extends the end time of the previous line. 41 | 1. If it was neither snapped nor linked, it simply adds lead in. 42 | 43 | For end time: 44 | 45 | 1. If end time is already snapped to keyframe, it does not make any changes to the end time. 46 | 1. Here's a special step that is only applicable when your keyframe snapping value is greater than 850 ms. Snapping to keyframes more than 850 ms away is not always the correct thing to do, hence this special step. If the script finds that there is a keyframe 850+ ms away from exact end, and you've allowed to snap to that distance in config, then it first checks cps of the line (without leadout). If cps is greater than 15, then it snaps to keyframe. If the cps is less than 15, then it either tries to add lead out to the line or extend the end time such that it is 500 ms away from keyframe whichever is lesser. 47 | 1. If above special case is not true(which is most of the case), it simply checks if there is a keyframe within time specified in the config and snaps to it. 48 | 1. If it did not snap, it simply adds lead out to the line. 49 | 50 | ## Config 51 | 52 | ![timer](./assets/timer.png) 53 | 54 | When you use the script for the first time, it uses values that I consider sane 55 | defaults. You are however free to change it and the script will perform as 56 | intended as long as the values you put are within reason. 57 | 58 | ### Creating a new preset 59 | 60 | Creating a new preset is as easy as changing the values in the GUI and hitting 61 | the `Create Preset` button. 62 | 63 | ![timer](./assets/timer2.png) 64 | 65 | You will then be asked the name of the preset. If you want to use the preset as 66 | soon as you create it, tick on `Set as Current Preset`. 67 | 68 | ### Actions on preset 69 | 70 | ![timer](./assets/timer3.png) 71 | 72 | Once you have a preset, you might want to perform some actions on them. First 73 | select the preset you want to act on in the left dropdown. Then select the 74 | action you want to perform on it in the right dropdown. Then finally click on 75 | `Modify Preset` button. 76 | 77 | #### Load 78 | 79 | This just loads the values of that preset in the GUI. 80 | 81 | #### Modify 82 | 83 | This modifies the preset with the values that is currently in the GUI. 84 | 85 | #### Delete 86 | 87 | This deletes the preset. Default preset cannot be deleted. 88 | 89 | #### Rename 90 | 91 | This renames the preset. 92 | 93 | #### Set Current 94 | 95 | This sets the preset and the current preset and its value will be used whenever you press the hotkey. 96 | -------------------------------------------------------------------------------- /.github/workflows/depctrl.yml: -------------------------------------------------------------------------------- 1 | name: depctrl 2 | 3 | on: 4 | push: 5 | paths: 6 | - "macros/*.lua" 7 | - "macros/*.moon" 8 | - "modules/**/*.lua" 9 | - "modules/**/*.moon" 10 | branches: 11 | - main 12 | 13 | jobs: 14 | depctrl: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Get-changed-files 22 | id: changed-files-comma 23 | uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46 24 | with: 25 | since_last_remote_commit: "true" 26 | separator: "," 27 | 28 | - name: Update sha1 and version 29 | run: | 30 | output_file="/tmp/SHA.json" 31 | while IFS= read -r file; do 32 | # Uses sha1sum to find sha1. Outputs in `sha1 filename` format. 33 | CHKSUM=$(sha1sum "${file}") 34 | 35 | # Get current date in ISO 8601 format 36 | DATE=$(date -I) 37 | 38 | # Get actual sha1 of new file 39 | SHA=$(echo "${CHKSUM}" | awk '{print $1}') 40 | 41 | # Get the full filename of the Aegisub-scripts 42 | FULL_FILE=$(echo "${CHKSUM}" | awk -F'/' '{print $NF}') 43 | 44 | # Get the folder of Aegisub-scripts or modules 45 | FOLDER=$(echo "${file}" | rev | cut -d/ -f2- | rev | cut -d/ -f2-) 46 | 47 | # Namespace is the filename stripped of their extension. 48 | # This same should be the first field in DependencyControl.json inside macros field for the sake of automation. 49 | # For example: My script is named phos.wave.lua. Therefore, the first level key is phos.wave 50 | NAMESPACE=$(echo "${FULL_FILE}" | sed "s|.moon||g;s|.lua||g") 51 | 52 | # Get version of the script or module 53 | if [[ ${FOLDER} == "macros" ]]; then 54 | # Provided that `script_version = "version"` is in the scripts - Spaces around '=' or lack of it is accounted for 55 | VERSION=$(grep -E 'script_version\s?=' "${FOLDER}/${FULL_FILE}" | cut -d '"' -f2) 56 | else 57 | VERSION=$(grep -E '^\s*version: ".+",\s*$' "${FOLDER}/${FULL_FILE}" | cut -d '"' -f2) 58 | fi 59 | 60 | # Check if the file is added to DependencyControl or not and if version could be found or not 61 | if grep -q "${NAMESPACE}" DependencyControl.json && [[ -n "${VERSION}" ]]; then 62 | 63 | # Check if this file has been modified in last commit. 64 | MODIFIED=$(echo ${{ steps.changed-files-comma.outputs.all_changed_files }}) 65 | 66 | # Change sha1, date and version if the file was modified in last commit 67 | if grep -q "${FULL_FILE}" <<< "${MODIFIED}"; then 68 | 69 | if [[ ${FOLDER} == "macros" ]]; then 70 | jq --tab ".macros.\"${NAMESPACE}\".channels.main.files[].sha1=\"${SHA}\" | .macros.\"${NAMESPACE}\".channels.main.version=\"${VERSION}\" | .macros.\"${NAMESPACE}\".channels.main.released=\"${DATE}\"" DependencyControl.json >"${output_file}" 71 | else 72 | jq --tab ".modules.\"phos.${NAMESPACE}\".channels.main.files[].sha1=\"${SHA}\" | .modules.\"phos.${NAMESPACE}\".channels.main.version=\"${VERSION}\" | .modules.\"phos.${NAMESPACE}\".channels.main.released=\"${DATE}\"" DependencyControl.json >"${output_file}" 73 | fi 74 | 75 | # If something goes wrong with jq operation, it outputs empty file. We don't want to commit it so a condition to check it. 76 | if [[ -s "${output_file}" ]]; then 77 | mv "${output_file}" DependencyControl.json 78 | echo "Successfully updated Dependency Control for file ${FULL_FILE}." 79 | else 80 | echo "Something went wrong while processing ${FULL_FILE}. The file is empty." 81 | fi 82 | fi 83 | else 84 | echo "Either the file ${FULL_FILE} is not added to Dependency Control or version could not be found in the script. Skipping changing hashes." 85 | fi 86 | done < <(find ./macros ./modules/** -name "*lua" -o -name "*.moon" -type f) 87 | - name: Commit changes 88 | env: 89 | COMMIT_MSG: | 90 | Automatic update of hashes and script version 91 | run: | 92 | git config user.name github-actions 93 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 94 | git add DependencyControl.json 95 | # Only commit and push if we have changes 96 | git diff --quiet && git diff --staged --quiet || (git commit -m "${COMMIT_MSG}"; git push) 97 | -------------------------------------------------------------------------------- /macros/phos.wave.lua: -------------------------------------------------------------------------------- 1 | -- Script information 2 | script_name = "Wave" 3 | script_description = "Make the string wavy" 4 | script_author = "PhosCity" 5 | script_version = "1.0.3" 6 | script_namespace = "phos.wave" 7 | 8 | local haveDepCtrl, DependencyControl, depRec = pcall(require, "l0.DependencyControl") 9 | if haveDepCtrl then 10 | depRec = DependencyControl({ 11 | feed = "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 12 | { "karaskel" }, 13 | }) 14 | depRec:requireModules() 15 | else 16 | require("karaskel") 17 | end 18 | 19 | local function stringToTable(text) 20 | local table = {} 21 | for i = 0, #text do 22 | table[i] = text:sub(i, i) 23 | end 24 | return table 25 | end 26 | 27 | local function wave(res, text, scale_x, scale_y, spacing, wave_time) 28 | local STING = stringToTable(text) 29 | local new_text = "" 30 | -- Credit for the original code below before modification: The0x539 31 | for j = 0, #STING do 32 | new_text = new_text 33 | .. string.format( 34 | "{\\fsp%.1f\\fscx%.1f\\fscy%.1f", 35 | spacing + 5 * math.sin(j), 36 | scale_x + 10 * math.sin(j), 37 | scale_y + 10 * math.sin(j) 38 | ) 39 | for i = 0, wave_time, res.frequency do 40 | local i2 = i + res.frequency 41 | new_text = new_text 42 | .. string.format( 43 | "\\t(%d,%d,\\fsp%.1f\\fscx%.1f\\fscy%.1f)", 44 | math.floor(i * 1000), 45 | math.floor(i2 * 1000), 46 | spacing + 5 * math.sin(res.fsp * i2 + j), 47 | scale_x + 10 * math.sin(res.fscx * i2 + j), 48 | scale_y + 10 * math.sin(res.fscy * i2 + j) 49 | ) 50 | end 51 | new_text = new_text .. string.format("}%s", STING[j]) 52 | end 53 | return new_text 54 | end 55 | 56 | local function main(subs, sel, res) 57 | local meta, styles = karaskel.collect_head(subs, false) 58 | for _, i in ipairs(sel) do 59 | if subs[i].class == "dialogue" then 60 | --default values 61 | local scale_x = 100 62 | local scale_y = 100 63 | local spacing = 0 64 | 65 | local line = subs[i] 66 | local tags = line.text:match("{\\[^}]-}") 67 | local text = line.text:gsub("{\\[^}]-}", "") 68 | 69 | -- Time and frequency 70 | local line_duration = line.end_time - line.start_time 71 | local total_wave_time = (line_duration + 100) / 1000 72 | if res.frequency >= total_wave_time then 73 | aegisub.log( 74 | "The frequency you provided should be lesser than the line duration i.e. " .. total_wave_time 75 | ) 76 | return 77 | end 78 | 79 | --get style data 80 | karaskel.preproc_line(subs, meta, styles, line) 81 | scale_x = line.styleref.scale_x 82 | scale_y = line.styleref.scale_y 83 | spacing = line.styleref.spacing 84 | 85 | if tags then 86 | -- get tags in line 87 | if line.text:match("\\fscx([^}\\]+)") then 88 | scale_x = line.text:match("\\fscx([^}\\]+)") 89 | end 90 | if line.text:match("\\fscy([^}\\]+)") then 91 | scale_y = line.text:match("\\fscy([^}\\]+)") 92 | end 93 | if line.text:match("\\fsp([^}\\]+)") then 94 | spacing = line.text:match("\\fsp([^}\\]+)") 95 | end 96 | line.text = tags .. wave(res, text, scale_x, scale_y, spacing, total_wave_time) 97 | line.text = line.text:gsub("}{", "") 98 | else 99 | line.text = wave(res, text, scale_x, scale_y, spacing, total_wave_time) 100 | end 101 | subs[i] = line 102 | end 103 | end 104 | end 105 | 106 | local function load_macro(subs, sel) 107 | --GUI 108 | local GUI = { 109 | { x = 0, y = 0, class = "label", label = "fscx strength: ", hint = "fscx strength" }, 110 | { x = 1, y = 0, class = "floatedit", name = "fscx", value = "2", hint = "fscx strength" }, 111 | { x = 0, y = 1, class = "label", label = "fscy strength: ", hint = "fscy strength" }, 112 | { x = 1, y = 1, class = "floatedit", name = "fscy", value = "2", hint = "fscy strength" }, 113 | { x = 0, y = 2, class = "label", label = "fsp strength: ", hint = "fsp strength" }, 114 | { x = 1, y = 2, class = "floatedit", name = "fsp", value = "2", hint = "fsp strength" }, 115 | { 116 | x = 0, 117 | y = 3, 118 | class = "label", 119 | label = "frequency should", 120 | }, 121 | { 122 | x = 1, 123 | y = 3, 124 | class = "label", 125 | label = " be lesser than the line duration in seconds", 126 | }, 127 | { x = 0, y = 4, class = "label", label = "frequency: ", hint = "more is slower" }, 128 | { x = 1, y = 4, class = "floatedit", name = "frequency", value = "0.1", hint = "more is slower" }, 129 | } 130 | 131 | local buttons = { "OK", "Cancel" } 132 | local pressed, res = aegisub.dialog.display(GUI, buttons) 133 | 134 | if pressed == "Cancel" then 135 | aegisub.cancel() 136 | end 137 | if pressed == "OK" then 138 | main(subs, sel, res) 139 | end 140 | 141 | aegisub.set_undo_point(script_name) 142 | end 143 | 144 | -- Register macro to Aegisub 145 | if haveDepCtrl then 146 | depRec:registerMacro(load_macro) 147 | else 148 | aegisub.register_macro(script_author .. "/" .. script_name, script_description, load_macro) 149 | end 150 | -------------------------------------------------------------------------------- /macros/phos.img2ass.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Phos-img2ass" 2 | export script_description = "Img2ass that is optimized to not be img2ass" 3 | export script_author = "PhosCity" 4 | export script_version = "0.1.6" 5 | export script_namespace = "phos.img2ass" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | { "ZF.main", url: "https://github.com/TypesettingTools/zeref-Aegisub-Scripts", 12 | feed: "https://raw.githubusercontent.com/TypesettingTools/zeref-Aegisub-Scripts/main/DependencyControl.json"}, 13 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 15 | }, 16 | } 17 | zf, Functional = depctrl\requireModules! 18 | logger = depctrl\getLogger! 19 | {:list} = Functional 20 | 21 | createGUI = -> 22 | dlg = { 23 | {x: 0, y: 0, width: 1, height: 1, class: "label", label: "Tolerance"}, 24 | {x: 1, y: 0, width: 1, height: 1, class: "intedit", name: "tolerance", value: 5, min: 0, max: 254}, 25 | {x: 0, y: 1, width: 1, height: 1, class: "label", label: "0 : Too Many Lines, Max Quality"}, 26 | {x: 0, y: 2, width: 1, height: 1, class: "label", label: "5 : Lesser Lines, Decent Quality"}, 27 | {x: 0, y: 3, width: 1, height: 1, class: "label", label: "10 : Mid Sweet Spot"}, 28 | {x: 0, y: 4, width: 1, height: 1, class: "label", label: ">20: Fewer Lines, Low Quality"}, 29 | {x: 0, y: 6, width: 1, height: 1, class: "label", label: "Warn if number of line exceeds"}, 30 | {x: 1, y: 6, width: 1, height: 1, class: "intedit", name: "linelimit", value: 700, min: 0}, 31 | } 32 | 33 | btn, res = aegisub.dialog.display dlg, {"OK", "Cancel"}, {"ok": "OK", "cancel": "Cancel"} 34 | aegisub.cancel! unless btn 35 | res 36 | 37 | data2hex = (data) -> 38 | return unless data 39 | {:b, :g, :r, :a} = data 40 | color = ("%02X%02X%02X")\format b, g, r 41 | alpha = ("%02X")\format 255 - a 42 | return color, alpha 43 | 44 | main = (sub, selected, act) -> 45 | res = createGUI! 46 | 47 | exts = "*.png;*.jpeg;*.jpe;*.jpg;*.jfif;*.jfi;*.bmp;*.dib" 48 | filename = aegisub.dialog.open "Open Image File", "", "", "Image extents (#{exts})|#{exts};", false, true 49 | aegisub.cancel! unless filename 50 | 51 | dlg = zf.dialog sub, selected, active 52 | sel = selected[#selected] 53 | img = zf.img filename 54 | img\setInfos! 55 | {:width, :height, :data} = img 56 | 57 | logger\log "Make Image found #{width * height} pixels in your image." 58 | linesOfSameColor, lineCount = {}, 0 59 | for y = 0, height - 1 60 | for x = 0, width - 1 61 | index = y * width + x 62 | aegisub.cancel! if aegisub.progress.is_cancelled! 63 | color, alpha = data2hex data[index] 64 | if alpha != "FF" 65 | shape = "m #{x} #{y} l #{x+1} #{y} #{x+1} #{y+1} #{x} #{y+1}" 66 | unless linesOfSameColor[color] 67 | linesOfSameColor[color] = {} 68 | lineCount += 1 69 | table.insert linesOfSameColor[color], shape 70 | logger\log "But I reduced them to #{lineCount} chunks of same colors." 71 | 72 | img = nil 73 | filename = nil 74 | 75 | finalTable = {} 76 | if res.tolerance == 0 77 | finalTable = linesOfSameColor 78 | else 79 | extractRGB = (color) -> 80 | b, g, r = color\match "(..)(..)(..)" 81 | tonumber(b, 16), tonumber(g, 16), tonumber(r, 16) 82 | 83 | logger\log "Now, I'll try to group them into chunks of similar colors to reduce the line count even more." 84 | lineCount = 0 85 | for color, lines in pairs linesOfSameColor 86 | aegisub.cancel! if aegisub.progress.is_cancelled! 87 | b, g, r = extractRGB color 88 | minDiff = math.huge 89 | for col, _ in pairs finalTable 90 | b1, g1, r1 = extractRGB col 91 | bdiff, gdiff, rdiff = math.abs(b-b1), math.abs(g-g1), math.abs(r-r1) 92 | sumDiff = bdiff + gdiff + rdiff 93 | if bdiff < res.tolerance and gdiff < res.tolerance and rdiff < res.tolerance and minDiff > sumDiff 94 | minDiff = sumDiff 95 | color = col 96 | unless finalTable[color] 97 | finalTable[color] = {} 98 | lineCount += 1 99 | finalTable[color] = list.join finalTable[color], lines 100 | logger\log "I reduced it to #{lineCount} lines." 101 | 102 | if res.linelimit < lineCount 103 | btn = aegisub.dialog.display { 104 | {x: 0, y: 0, width: 1, height: 1, class: "label", label: "The number of lines to be inserted exceeds the limit you set."}, 105 | {x: 0, y: 1, width: 1, height: 1, class: "label", label: "Number of Lines: #{lineCount}"}, 106 | {x: 0, y: 2, width: 1, height: 1, class: "label", label: "Limit: #{res.linelimit}"}, 107 | {x: 0, y: 3, width: 1, height: 1, class: "label", label: "Do you want to proceed to insert those lines?"}, 108 | }, {"Yes", "No"} 109 | if btn == "No" 110 | logger\log "CANCELLED!" 111 | aegisub.cancel! 112 | logger\log "Now inserting those #{lineCount} lines." 113 | logger\log "This may take some time." 114 | 115 | linesOfSameColor = nil 116 | res = nil 117 | 118 | for color, shapeTable in pairs finalTable 119 | aegisub.cancel! if aegisub.progress.is_cancelled! 120 | shape = zf.clipper(table.concat shapeTable, " ")\simplify("even_odd")\build "line" 121 | line = sub[act] 122 | line.text = "{\\an7\\pos(0,0)\\fscx100\\fscy100\\bord0\\shad0\\frz0\\c&H#{color}&\\alpha&H00&\\p1}" .. shape 123 | dlg\insertLine line, sel 124 | logger\log "FINISHED!" 125 | 126 | depctrl\registerMacro main 127 | -------------------------------------------------------------------------------- /macros/phos.CombineDrawings.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Combine Drawings" 2 | export script_description = [[Combine drawings that have same primary color in a selection. 3 | Maintains positioning and converts scale as well as alignment.]] 4 | export script_version = "0.1.1" 5 | export script_author = "PhosCity" 6 | export script_namespace = "l0.CombineDrawings" 7 | 8 | DependencyControl = require "l0.DependencyControl" 9 | 10 | rec = DependencyControl{ 11 | feed: "", 12 | { 13 | {"a-mo.LineCollection", version: "1.3.0", url: "https://github.com/TypesettingTools/Aegisub-Motion", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 15 | {"l0.ASSFoundation", version: "0.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 17 | {"l0.Functional", version: "0.3.0", url: "https://github.com/TypesettingTools/Functional", 18 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 19 | } 20 | } 21 | 22 | LineCollection, ASS, Functional = rec\requireModules! 23 | import util from Functional 24 | logger = rec\getLogger! 25 | 26 | table_contains = (tbl, x) -> 27 | for item in *tbl 28 | return true if item == x 29 | return false 30 | 31 | tableLength = (tbl) -> 32 | count = 0 33 | for _ in pairs(tbl) do count = count + 1 34 | count 35 | 36 | combineDrawings = (sub, sel) -> 37 | lines = LineCollection sub, sel 38 | lineCnt = #lines.lines 39 | return if lineCnt == 0 40 | 41 | colorTable, mergedLines, targetSection = {}, {}, {} 42 | targetScaleX, targetScaleY = 1, 1 43 | target = {name, ASS\createTag(name, value) for name, value in pairs {"align": 7, "scale_x": 100, "scale_y": 100}} 44 | local targetLine 45 | 46 | col = (lines, line, i) -> 47 | data = ASS\parse line 48 | tags = (data\getEffectiveTags -1, true, true, false).tags 49 | b, g, r = tags.color1\getTagParams! 50 | color = util.ass_color(r, g, b) 51 | colorTable[color] or= {} 52 | table.insert colorTable[color], i 53 | lines\runCallback col, true 54 | colLength = tableLength(colorTable) 55 | 56 | count = 1 57 | for key, value in pairs colorTable 58 | aegisub.progress.task "Merging lines with color %d out of %d..."\format count, colLength 59 | aegisub.progress.set 100*count/colLength 60 | count += 1 61 | lineCb = (lines, line, i) -> 62 | aegisub.cancel! if aegisub.progress.is_cancelled! 63 | if table_contains value, i 64 | data = ASS\parse line 65 | pos, align = data\getPosition! 66 | tags = (data\getEffectiveTags -1, true, true, false).tags 67 | targetLine = data if i == value[1] 68 | local haveTextSection 69 | 70 | data\callback (section) -> 71 | if section.class == ASS.Section.Drawing 72 | -- determine target drawing section to merge drawings into 73 | targetSection = section if i == value[1] 74 | -- get a copy of the position tag which needs to be 75 | -- applied as an offset to the drawing 76 | off = pos.class == ASS.Tag.Move and pos.startPos\copy! or pos\copy! 77 | 78 | -- determine the top/left bounds of the drawing in order to make 79 | -- the drawing start at the coordinate origin 80 | bounds = section\getBounds! 81 | -- trim drawing in order to scale shapes without causing them to move 82 | section\sub bounds[1] 83 | -- add the scaled bounds to our offset 84 | scaleX, scaleY = tags.scale_x.value/100, tags.scale_y.value/100 85 | off\add bounds[1]\mul scaleX, scaleY 86 | facX, facY = scaleX / targetScaleX, scaleY / targetScaleY 87 | unless facX == 1 and facY == 1 88 | section\mul facX, facY 89 | -- now apply the position offset scaled by the target fscx/fscy values 90 | section\add off\div targetScaleX, targetScaleY 91 | 92 | -- set intermediate point of origin alignment 93 | unless align\equal 7 94 | ex = section\getExtremePoints true 95 | srcOff = align\getPositionOffset ex.w, ex.h 96 | section\sub srcOff 97 | 98 | if i != value[1] 99 | -- insert contours into first line, create a drawing section if none exists 100 | targetSection or= (targetLine\insertSections ASS.Section.Drawing!)[1] 101 | targetSection\insertContours section 102 | return false 103 | 104 | elseif section.class == ASS.Section.Text 105 | haveTextSection or= true 106 | 107 | if i != value[1] 108 | -- remove drawings from original lines and mark empty lines for deletion 109 | if haveTextSection then data\commit! 110 | else mergedLines[#mergedLines+1] = line 111 | 112 | 113 | -- process all selected lines 114 | lines\runCallback lineCb, true 115 | 116 | -- update tags and aligment 117 | targetLine\replaceTags [tag for _,tag in pairs target] 118 | unless target.align\equal 7 119 | ex = targetSection\getExtremePoints true 120 | off = target.align\getPositionOffset ex.w, ex.h 121 | targetSection\add off 122 | 123 | pos, align = targetLine\getPosition! 124 | bounds = targetSection\getBounds! 125 | targetSection\sub bounds[1] 126 | if pos.class == ASS.Tag.Move 127 | pos.endPos\sub pos.startPos 128 | pos.endPos\add bounds[1] 129 | pos.startPos\set bounds[1].x, bounds[1].y 130 | else 131 | targetLine\replaceTags{ASS\createTag "position", bounds[1]} 132 | 133 | targetLine\commit! 134 | lines\replaceLines! 135 | lines\deleteLines mergedLines 136 | 137 | 138 | rec\registerMacro combineDrawings 139 | -------------------------------------------------------------------------------- /docs/Abacus.md: -------------------------------------------------------------------------------- 1 | **Available in Dependency Control** 2 | 3 | [Source to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/source/phos.Abacus.norg) 4 | 5 | [Link to script](https://github.com/PhosCity/Aegisub-Scripts/blob/main/macros/phos.Abacus.moon) 6 | 7 | The script `Abacus` allows you to recalculate the value of tags in the line. The GUI of this script is dynamically generated such that only tags that are available in the line are shown in the GUI. 8 | 9 | ![image](./assets/abacusGUI.png){: style="height:611px;width:796px"} 10 | 11 | # Buttons 12 | 13 | ![image](./assets/abacusbuttons.png){: style="height:41px;width:352px"} 14 | 15 | ## Add 16 | 17 | This will increase/lower value of selected tags by adding the user specified number. If the input is positive, the tag value will increase and if the input is negative, the tag value will decrease. 18 | 19 | ## Multiply 20 | 21 | This will increase/lower value of selected tags based on given percentage. 22 | 23 | For example, if the user specifies 150, you will get 50% increase in the value of specified tag. If user specifies 50, you will get a 50% decrease in the value of the specified tags. 24 | 25 | ## Reset GUI 26 | 27 | The checkboxes you ticked as well as the values you typed last time are remembered and already populate the GUI when you open the script. If you wish to unset all of them at once to get a clean GUI, you click this button. This will close the current GUI and load a clean new GUI. 28 | 29 | # Input 30 | 31 | There are a few types of input a user can provide. 32 | 33 | ## Time 34 | 35 | Whenever you're dealing with time like line's start time or end time or karaoke tags, the input provided is in time. You can input time in various ways that makes sense. For example, it makes most sense to input time in centiseconds when dealing with karaoke tags. 36 | 37 | | Format | Example | Remarks | 38 | | :--------- | :--------- | :----------------------------------------- | 39 | | h:mm:ss.ms | 0:01:20.03 | | 40 | | n | 30 | Time in miliseconds | 41 | | nms | 30ms | Time in miliseconds | 42 | | ncs | 30cs | Time in centiseconds | 43 | | ns | 30s | Time in seconds | 44 | | nm | 30m | Time in minutes | 45 | | nh | 3h | Time in hours | 46 | | nf | 3f | Time in frames (Requires video to be open) | 47 | 48 | # Color 49 | 50 | For all color related tags, you input r, g and b value separated by comma that will be added/multiplied to current tag. The resulting values are clamped between 0 and 255. 51 | 52 | | Format | Example | 53 | | :------ | :------- | 54 | | r, g, b | 20,30,40 | 55 | 56 | ## Number 57 | 58 | If the input is neither time or color, it will just be a number. 59 | 60 | ## Lua Math Expression 61 | 62 | While its utility is debatable, this script can accept any valid lua math expressions as input. 63 | 64 | Examples. 65 | 66 | ```lua 67 | 5+3 68 | math.sin(5) 69 | math.sin(3) 70 | math.random(-10,10) 71 | ``` 72 | 73 | Lua cannot do float random by default so there is an additional function accpeted: `math.randomfloat(min, max)`. 74 | 75 | # Input Boxes 76 | 77 | There are two input boxes. Let's call them x-input box and y-input box for now as shown in image below. 78 | 79 | ![image](./assets/abacusinputbox.png){: style="height:72px;width:398px"} 80 | 81 | Most of the tags have only one parameter and will use x-input box. There are tags that use both input boxes. Most of them have to do with time or coordinates. The following table shows tags that use both boxes and all other tags use only x-input box. 82 | 83 | | Tag | x-input box | y-input box | 84 | | :----------------- | :-------------------------- | :-------------------------- | 85 | | time | start time of line | end time of line | 86 | | pos | start time | end time | 87 | | pos/org/move/clip | x coordinate | y coordinate | 88 | | move | x coordinate | y coordinate | 89 | | move(t1,t2) | start time of move tag | end time of move tag | 90 | | org | x coordinate | y coordinate | 91 | | fad | start time of fade tag | end time of fad tag | 92 | | clip (Rectangle) | all x coordinate of clip | all y coordinate of clip | 93 | | clip_rect (x1,y1) | x1 coordinate of clip | y1 coordinate of clip | 94 | | clip_rect (x2,y2) | x2 coordinate of clip | y2 coordinate of clip | 95 | | iclip (Rectangle) | all x coordinate of iclip | all y coordinate of iclip | 96 | | iclip_rect (x1,y1) | x1 coordinate of iclip | y1 coordinate of iclip | 97 | | iclip_rect (x2,y2) | x2 coordinate of iclip | y2 coordinate of iclip | 98 | | clip (Vector) | all x coordinate of clip | all y coordinate of clip | 99 | | iclip (Vector) | all x coordinate of iclip | all y coordinate of iclip | 100 | | shape | all x coordinate of a shape | all y coordinate of a shape | 101 | | transform (t1,t2) | start time of a transform | end time of a transform | 102 | 103 | # Increase with each line 104 | 105 | This option will be shown to you if you have selected multiple lines. If this is ticked, your input value will increase by the input amount for each iteration of selected lines. For example, if your input is 1, the first line will have input of 1, second will have input of 2, third line will have input of 3 and so on. 106 | 107 | This works for all kinds of inputs mentioned above. 108 | -------------------------------------------------------------------------------- /test/phos.EditTags.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 9715-makepkg-7f928e8d6 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | YCbCr Matrix: None 9 | PlayResX: 1920 10 | PlayResY: 1080 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:24000/1001:40000:1920:1080:47:163:254: 15 | Video AR Value: 1.777778 16 | Video Zoom Percent: 0.375000 17 | Active Line: 5 18 | Video Position: 28036 19 | 20 | [V4+ Styles] 21 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 22 | Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1 23 | Style: asdf,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1 24 | 25 | [Events] 26 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 27 | Dialogue: 0,0:19:28.68,0:19:28.81,Default,,0,0,0,empty line, 28 | Dialogue: 0,0:19:28.93,0:19:29.06,Default,,0,0,0,Only comment,{GOLD ROGER, KING OF THE PIRATES} 29 | Dialogue: 1,0:19:28.81,0:19:28.93,Default,,0,0,0,Only Comment + Text,{This is a comment}This is a line 30 | Dialogue: 0,0:19:29.06,0:19:29.18,Default,,0,0,0,Only Text,This is a line 31 | Dialogue: 0,0:19:29.18,0:19:29.31,Default,,0,0,0,Only start tags,{\pos(628.65,140.47)\fscx100\fscy100\bord2\1c&H1718C3&\blur1}This is a line 32 | Dialogue: 0,0:19:29.31,0:19:29.43,Default,,0,0,0,Two sections,{\bord5\blur0.8}First Section{\fnKnorke\bord2.72\c&H69594D&}Second Section 33 | Dialogue: 0,0:19:29.31,0:19:29.43,Default,,0,0,0,No start Tags,First Section{\fscx42.03\fscy84.07\frz0.776\fry-2.208\frx1.971\fnKnorke\bord2.72\xbord0\ybord0\c&H69594D&\blur0.72}Second Section 34 | Dialogue: 0,0:19:29.56,0:19:29.69,Default,,0,0,0,gradient,{\an5\pos(845.25,230.04)\org(886.67,217.4)\fscx34.37\fscy68.75\frz0.776\fry-2.208\frx1.971\fnKnorke\bord2.22\xbord0\ybord0\c&H69594D&\blur0.59}T{\blur0.75}h{\blur0.91}i{\blur1.06}s {\blur1.38}i{\blur1.53}s {\blur1.85}a {\blur2.17}l{\blur2.32}i{\blur2.48}n{\blur2.64}e {\blur2.95}w{\blur3.11}i{\blur3.27}t{\blur3.42}h {\blur3.74}g{\blur3.9}r{\blur4.05}a{\blur4.21}d{\blur4.37}i{\blur4.53}e{\blur4.68}n{\blur4.84}t{\blur5} 35 | Dialogue: 0,0:19:29.69,0:19:29.81,asdf,,0,0,0,move,{\an5\org(886.67,217.4)\fscx32.51\fscy65.03\frz0.776\fry-2.208\frx1.971\fnKnorke\bord2.1\xbord0\ybord0\c&H69594D&\blur0.56\move(846.18,249.68,1238.18,660.35,20,20)}This is a line 36 | Dialogue: 0,0:19:29.81,0:19:29.94,asdf,,0,0,0,transform,{\an5\org(886.67,217.4)\pos(847.11,269.33)\fscx30.65\fscy61.31\frz0.776\fry-2.208\frx1.971\fnKnorke\bord1.98\xbord0\ybord0\1c&H69594D&\blur0.53\t(0,200,\bord0)\t(200,500,\blur25)}This is a line 37 | Dialogue: 0,0:19:29.94,0:19:30.06,asdf,,0,0,0,rect clip,{\an5\pos(849.46,275.32)\org(886.67,217.4)\fscx29.72\fscy59.46\frz0.776\fry-2.208\frx1.971\fnKnorke\bord1.92\xbord0\ybord0\1c&H69594D&\blur0.51\clip(584,164,1292,532)}This is a line 38 | Dialogue: 0,0:19:30.06,0:19:30.19,asdf,,0,0,0,vect clip,{\an5\org(886.67,217.4)\pos(856.35,287.73)\fscx27.86\fscy55.74\frz0.776\fry-2.208\frx1.971\fnKnorke\bord1.8\xbord0\ybord0\c&H69594D&\blur0.48\clip(m 592 508 b 610 386 581.85 374.56 628 264 677.85 144.56 680.27 114.39 784 48 880.27 -13.61 905.23 14.82 1028 8 1157.23 0.82 1168.53 -18.11 1288 20 1400.53 55.89 1421.31 63.71 1492 156 1565.31 251.71 1557.93 272.52 1576 396 1593.93 518.52 1629.29 567.35 1564 648 1493.29 735.35 1434.5 728.89 1304 732 1182.5 734.89 1184.49 676.46 1060 660 942.49 644.46 928.34 703.19 820 668)}This is a line 39 | Dialogue: 0,0:19:30.19,0:19:30.61,asdf,,0,0,0,fade,{\an5\org(886.67,217.4)\pos(864.84,306.73)\fade(255,0,255,0,200,300,400)\fscx25.54\fscy51.1\frz0.776\fry-2.208\frx1.971\fnKnorke\bord1.65\xbord0\ybord0\c&H69594D&\blur0.44}This is a line 40 | Dialogue: 0,0:10:52.26,0:10:52.59,Default,,0,0,0,shape,{\an7\pos(0,0)\c&H3F2916&\3c&H3F2916&\alpha&H01&\fscx100\fscy100\bord1\shad0\frz0\p1}m 1747 143 l 1753 143 1753 145.5 1751 150 b 1749 150.8 1748.3 150 1749 147.5 l 1747 143 m 1777.5 151 b 1779.6 157.9 1783.2 163.3 1788.5 167 1790 167.5 1790.5 167 1790 165.5 l 1792 164.5 1793.5 167 1797.5 168 1809.5 176 1815 177 1815 202.5 1813.5 204 1811.5 205 1809.5 204 1806 210 1798.5 215 1794.5 216 1786 223.5 1784.5 226 1782.5 223 1781 226.5 1783 230.5 1779 238.5 1779 241.5 b 1781.5 243.3 1782.5 246.2 1782 250 l 1788 254.5 b 1789.9 255.6 1790.5 258 1790 261.5 l 1789 264.5 1794 271.5 1794 279.5 1793 279.5 1792 276.5 1792 274.5 1786 266.5 1784 262.5 1784 258.5 1779 252.5 1778 248 b 1780 248.8 1780.7 248 1780 245.5 l 1771 240 1769 232 1766 231.5 1767 229 b 1768.8 229.8 1769.4 229.3 1769 227.5 l 1772 225.5 1772 223.5 1773 219 b 1774.8 219.8 1775.4 219.3 1775 217.5 l 1777 215.5 1779 207.5 b 1777.5 206.7 1777.2 204.7 1778 201.5 1779 199.5 1780 199.5 1781 201.5 l 1785 206.5 1786 215 1789 213.5 1791 206.5 1793.5 204 1799.5 202 b 1800.5 204.3 1801.5 204.7 1802.5 203 l 1807.5 204 1814 196.5 1813 194.5 1813 192.5 1811 185.5 1808 181 1795.5 172 1788.5 171 1785 167.5 1777 153.5 1777.5 151 m 1779.5 229 l 1778 233 1781 231.5 1779.5 229 m 1809.5 195 b 1811.5 194.5 1812 195.5 1811 198 l 1809 197.5 1809.5 195 m 1793.5 283 l 1795 283.5 1793 287 1786.5 291 1788.5 288 1792 285.5 1793.5 283 m 1784.5 294 l 1788 299.5 1785 300.5 1786 303 b 1787.8 302.3 1788.4 302.8 1788 304.5 l 1786.5 304 b 1784.5 303.4 1784 304.6 1785 307.5 1786.5 308.3 1786.8 310.3 1786 313.5 l 1785 318.5 1780.5 323 b 1778.3 323.8 1777.4 323.3 1778 321.5 1781.8 317.9 1783.1 311.9 1782 303.5 l 1778 296.5 1781.5 295 b 1782.5 296.7 1783.5 296.3 1784.5 294 m 1781.153 300.75 l 1782.5 298 b 1781.653 301.75 1781.153 300.75 1784 301 l 1782.5 298 m 1573.5 295 l 1574 301.5 1573 301.5 1573.5 295 m 1793.5 303 l 1800.5 313 1808 317.5 1805.5 319 b 1804.5 317 1802.4 316.1 1799 316.5 l 1804 315.5 1796 309.5 1793.5 303 m 1789.5 307 l 1791 312.5 1789 311.5 1789.5 307 m 1809.5 318 l 1811 320.5 1810 320.5 1809.5 318 m 1778.5 330 l 1779 332.5 1778 334.5 1781 338.5 b 1780.7 343.5 1781.7 347.2 1784 349.5 l 1782.5 355 b 1782.6 351.6 1781.4 349.4 1779 348.5 l 1777 336.5 1777 334.5 1778.5 330 m 1812.5 355 b 1815 354.3 1815.8 355 1815 357 1811.3 355.5 1809.9 356.7 1811 360.5 l 1806 367.5 1804 378.5 1815 386.5 1813.5 386 1804 380.5 1803 371.5 1807 364.5 1808 358 1812.5 355 m 1655.5 427 l 1656 429.5 1655 429.5 1655.5 427 41 | -------------------------------------------------------------------------------- /macros/phos.AddGrain.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Add Grain" 2 | export script_description = "Add static and dynamic grain" 3 | export script_version = "1.1.5" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.AddGrain" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 14 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 17 | "Yutils" 18 | } 19 | } 20 | LineCollection, ASS, Functional, Yutils = depctrl\requireModules! 21 | logger = depctrl\getLogger! 22 | {:list, :util} = Functional 23 | 24 | dialog = { 25 | {x: 0, y: 0, class: "label", label: "Grain Intensity", hint: "Higher the number, greater the intensity"} 26 | {x: 1, y: 0, class: "intedit", min: 1, value: 1, name: "intensity"} 27 | {x: 0, y: 1, class: "label", label: "Color of grain:"} 28 | {x: 0, y: 2, class: "label", label: "Layer 1"} 29 | {x: 1, y: 2, class: "color", value: "&HFFFFFF&", name: "color1"} 30 | {x: 0, y: 3, class: "label", label: "Layer 2"} 31 | {x: 1, y: 3, class: "color", value: "&H000000&", name: "color2"} 32 | } 33 | 34 | --- Checks if the grain font is installed and informs the user 35 | isGrainInstalled = -> 36 | message = "It seems you have not installed grain font. 37 | The script will proceed but will not look as intended unless you install the font. 38 | You can install it from following link: 39 | https://github.com/PhosCity/Aegisub-Scripts/tree/main/misc/Grain%20Font" 40 | 41 | for font in *Yutils.decode.list_fonts! 42 | return if font.name == "Grain" and font.longname == "Grain Regular" 43 | logger\log message 44 | 45 | 46 | --- Randomize a character by returning any character among 0-9a-zA-z!"',.:;? 47 | ---@return string or integer 48 | randomize = -> 49 | ascii = list.join [x for x = 48, 57], [x for x = 65, 90], [x for x = 97, 122], {33, 34, 39, 44, 46, 58, 59, 63} 50 | string.char ascii[math.random(1, #ascii)] 51 | 52 | createGui = -> 53 | btn, res = aegisub.dialog.display dialog, {"OK", "Cancel"}, {"ok": "OK", "cancel": "Cancel"} 54 | aegisub.cancel! unless btn 55 | 56 | -- Save GUI configuration 57 | local configEntry 58 | for key, value in pairs res 59 | for i = 1, #dialog 60 | configEntry = dialog[i] 61 | continue unless configEntry.name == key 62 | if configEntry.value 63 | configEntry.value = value 64 | elseif configEntry.text 65 | configEntry.text = value 66 | break 67 | return res 68 | 69 | --- Main processing function 70 | ---@param mode string "dense" or "normal" 71 | ---@param sub table subtitle object 72 | ---@param sel table selected lines 73 | main = (useGui, mode) -> 74 | (sub, sel) -> 75 | isGrainInstalled! 76 | 77 | --- Create an ASSFoundation color tag object 78 | ---@param colorString string or table ass color string or a table with {b, g, r} values 79 | ---@param colorType string color tagname as understood by ASSFoundation 80 | ---@return table ASSFoundation color tag object 81 | createColor = (colorString, colorType) -> 82 | local r, g, b 83 | if type(colorString) == "string" 84 | r, g, b = util.extract_color(colorString) 85 | elseif type(colorString) == "table" 86 | b, g, r = unpack colorString 87 | return ASS\createTag colorType, b, g, r 88 | 89 | toDelete, toAdd = {}, {} 90 | local intensity, firstColor, secondColor 91 | if useGui 92 | res = createGui! 93 | intensity = res.intensity 94 | firstColor = createColor res.color1, "color1" 95 | secondColor = createColor res.color2, "color1" 96 | intensity = intensity or 1 97 | firstColor = firstColor or createColor {255, 255, 255}, "color1" 98 | secondColor = secondColor or createColor {0, 0, 0}, "color1" 99 | 100 | lines = LineCollection sub, sel 101 | return if #lines.lines == 0 102 | cb = (lines, line, i) -> 103 | data = ASS\parse line 104 | table.insert toDelete, line 105 | 106 | -- Pure white layer 107 | data\callback ((section) -> section\replace "!!", randomize), ASS.Section.Text 108 | data\removeTags {"fontname", "outline", "shadow", "color1"} 109 | 110 | data\insertTags { 111 | ASS\createTag 'fontname', "Grain" 112 | ASS\createTag 'outline', 0 113 | ASS\createTag 'shadow', 0 114 | ASS\createTag 'bold', 0 115 | firstColor 116 | } 117 | if mode == "dense" 118 | data\removeTags {"color3", "color4", "alpha1", "alpha3", "shadow", "shadow_x", "shadow_y"} 119 | data\insertTags { 120 | createColor {255, 255, 255}, "color3" 121 | createColor {255, 255, 255}, "color4" 122 | ASS\createTag 'alpha1', 0xFE 123 | ASS\createTag 'alpha3', 0xFF 124 | ASS\createTag 'shadow', 0.01 125 | } 126 | data\cleanTags! 127 | table.insert toAdd, ASS\createLine { line } 128 | 129 | -- Pure black layer 130 | data\callback ((section) -> section\replace "[^\\N]", randomize), ASS.Section.Text 131 | data\replaceTags secondColor 132 | if mode == "dense" 133 | data\replaceTags { 134 | createColor {0, 0, 0}, "color3" 135 | createColor {0, 0, 0}, "color4" 136 | } 137 | table.insert toAdd, ASS\createLine { line } 138 | 139 | -- Start iteration 140 | for i = 1, intensity 141 | aegisub.cancel! if aegisub.progress.is_cancelled! 142 | aegisub.progress.task "Completed #{i} of #{intensity} iteration..." 143 | aegisub.progress.set 100*i/intensity 144 | lines\runCallback cb, true 145 | 146 | -- Add lines 147 | for ln in *toAdd 148 | lines\addLine ln 149 | 150 | lines\insertLines! 151 | lines\deleteLines toDelete 152 | 153 | 154 | -- Register macros 155 | depctrl\registerMacros({ 156 | { "Add grain", "Add grain", main false, "normal" }, 157 | { "Add dense grain", "Add dense grain", main false, "dense" }, 158 | { "GUI", "Gui for Add Grain script", main true }, 159 | }) 160 | -------------------------------------------------------------------------------- /macros/phos.AutoGradient.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Auto Gradient" 2 | export script_description = "Automatically color gradient the line." 3 | export script_version = "0.0.6" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.AutoGradient" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 14 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | { "l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" }, 17 | {"phos.AssfPlus", version: "1.0.2", url: "https://github.com/PhosCity/Aegisub-Scripts", 18 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 19 | }, 20 | } 21 | LineCollection, ASS, Functional, AssfPlus = depctrl\requireModules! 22 | -- logger = depctrl\getLogger! 23 | { :util, :math } = Functional 24 | { :lineData, :_tag } = AssfPlus 25 | 26 | 27 | getPointsBetweenCoordinates = (startCoord, endCoord) -> 28 | x1, y1 = table.unpack startCoord 29 | x2, y2 = table.unpack endCoord 30 | dist = math.vector2.distance x1, y1, x2, y2 31 | points = {} 32 | for i = 1, dist 33 | m1 = i 34 | m2 = dist - i 35 | x = (m1 * x2 + m2 * x1)/(m1 + m2) 36 | y = (m1 * y2 + m2 * y1)/(m1 + m2) 37 | table.insert points, {x, y} 38 | points 39 | 40 | 41 | getColor = (frame, x, y) -> 42 | color = frame\getPixelFormatted(x, y) 43 | color 44 | 45 | 46 | colorsAreAlmostSame = (color1, color2) -> 47 | if color1 and color2 48 | deltaEValue = _tag.color.getDeltaE color1, color2 49 | return true if deltaEValue < 0.5 50 | return false 51 | 52 | 53 | clipGradient = (data, lines, line, clipTable, mode, frame) -> 54 | -- Collect bounding box of the line 55 | x1, y1, x2, y2 = lineData.getBoundingBox data, true, _, true 56 | 57 | gradientTable = {} 58 | clipCnt = #clipTable 59 | 60 | -- Find colors for intermediate points 61 | local prevColor 62 | if mode == "vertical" 63 | for j = y1, y2 64 | currPercent = (j - y1) / (y2 - y1) 65 | index = math.floor(math.round(currPercent * clipCnt)) 66 | index = math.max(index, 1) 67 | x, y = table.unpack clipTable[index] 68 | color = getColor frame, x, y 69 | 70 | if colorsAreAlmostSame(prevColor, color) 71 | gradientTable[#gradientTable][4] = j+1 72 | else 73 | table.insert gradientTable, {x1, j, x2, j+1, color} 74 | prevColor = color 75 | else 76 | for j = x1, x2 77 | currPercent = (j - x1) / (x2 - x1) 78 | index = math.floor(math.round(currPercent * clipCnt)) 79 | index = math.max(index, 1) 80 | x, y = table.unpack clipTable[index] 81 | color = getColor frame, x, y 82 | 83 | if colorsAreAlmostSame(prevColor, color) 84 | gradientTable[#gradientTable][3] = j+1 85 | else 86 | table.insert gradientTable, {j, y1, j+1, y2, color} 87 | prevColor = color 88 | 89 | -- Finally create new lines 90 | for item in *gradientTable 91 | leftX, leftY, rightX, rightY, color = table.unpack item 92 | r, g, b = util.extract_color color 93 | data\replaceTags {ASS\createTag "clip_rect", leftX, leftY, rightX, rightY} 94 | data\replaceTags {ASS\createTag "color1", b, g, r} 95 | lines\addLine ASS\createLine {line} 96 | 97 | 98 | gradientByCharacter = (data, clipTable, frame) -> 99 | clipCnt = #clipTable 100 | styleRef = data\getStyleRef! 101 | text = data\copy!\stripTags!\stripComments!\getString! 102 | 103 | charWidth = {} 104 | totalWidth = aegisub.text_extents styleRef, text 105 | currWidth = 0 106 | for char in text\gmatch "." 107 | width = aegisub.text_extents styleRef, char 108 | table.insert charWidth, currWidth / totalWidth 109 | currWidth += width 110 | 111 | processedChar = 0 112 | sections = {} 113 | data\callback (section) -> 114 | if section.class != ASS.Section.Text 115 | table.insert sections, section 116 | else 117 | text = section.value 118 | for char in text\gmatch "." 119 | processedChar += 1 120 | index = math.floor(math.round(charWidth[processedChar] * clipCnt)) 121 | index = math.max(index, 1) 122 | 123 | x, y = table.unpack clipTable[index] 124 | color = getColor frame, x, y 125 | r, g, b = util.extract_color color 126 | 127 | table.insert sections, ASS.Section.Tag {ASS\createTag "color1", b, g, r} 128 | table.insert sections, ASS.Section.Text char 129 | 130 | data\removeSections 1 , #data.sections 131 | data\insertSections sections 132 | data\cleanTags 0 133 | data\commit! 134 | 135 | 136 | main = (mode) -> 137 | (sub, sel) -> 138 | 139 | lines = LineCollection sub, sel 140 | return if #lines.lines == 0 141 | 142 | -- Initialize some variables 143 | to_delete = {} 144 | currentFrame = aegisub.project_properties!.video_position 145 | frame = aegisub.get_frame(currentFrame, false) 146 | 147 | lines\runCallback (lines, line, i) -> 148 | aegisub.cancel! if aegisub.progress.is_cancelled! 149 | table.insert to_delete, line 150 | 151 | data = ASS\parse line 152 | 153 | -- Collect vector clip 154 | clipTable = {} 155 | clip = data\removeTags "clip_vect" 156 | return if #clip == 0 157 | 158 | for index, cnt in ipairs clip[1].contours[1].commands -- Is this the best way to loop through co-ordinate? 159 | break if index == 3 160 | x, y = cnt\get! 161 | table.insert clipTable, {x, y} 162 | 163 | -- Find all the points between two points of clip 164 | clipTable = getPointsBetweenCoordinates(clipTable[1], clipTable[2]) 165 | 166 | if mode == "gbc" 167 | gradientByCharacter data, clipTable, frame 168 | else 169 | clipGradient data, lines, line, clipTable, mode, frame 170 | 171 | frame = nil 172 | collectgarbage! 173 | 174 | if mode == "gbc" 175 | lines\replaceLines! 176 | else 177 | lines\insertLines! 178 | lines\deleteLines to_delete 179 | 180 | 181 | depctrl\registerMacros({ 182 | {"Horizontal", "Horizontal Gradient", main "horizontal"}, 183 | {"Vertical", "Vertical Gradient", main "vertical"}, 184 | {"Gradient by Character", "Gradient by Character", main "gbc"}, 185 | }) 186 | -------------------------------------------------------------------------------- /macros/phos.qcreport.moon: -------------------------------------------------------------------------------- 1 | export script_name = "QC Report" 2 | export script_description = "Write and generate QC reports" 3 | export script_author = "PhosCity" 4 | export script_namespace = "phos.qcreport" 5 | export script_version = "1.0.3" 6 | 7 | default_config = 8 | section: {"Timing", "Typesetting", "Editing"}, 9 | itemTiming: { "Too much lead in.", "Too much lead out.", "Not enough lead in.", "Not enough lead out.", "Snap to keyframes.", "Link lines.", "Mouth flap." }, 10 | itemTypesetting: { "Too much blur.", "Not enough blur.", "Color mismatch.", "Mask fail.", "Incorrect tracking.", "Missing sign.", "Incorrect position.", 11 | "Banding visible.", "Incorrect perspective.", "Font mismatch.", "Incorrect fade.", "Incorrect size." } 12 | itemEditing: {} 13 | 14 | DependencyControl = require "l0.DependencyControl" 15 | depctrl = DependencyControl({}) 16 | config = depctrl\getConfigHandler(default_config, "config", false) 17 | logger = depctrl\getLogger! 18 | 19 | show_hour = false 20 | 21 | 22 | config_setup = () -> 23 | config\load() 24 | opt = config.c 25 | 26 | value = table.concat opt.section, "\n" 27 | 28 | conf_dlg = { 29 | { x: 0, y: 0, width: 1, height: 1, class: "label", label: "Add your sections below:", }, 30 | { x: 0, y: 1, width: 15, height: math.min(#opt.section+2, 5), class: "textbox", value: value, name: "section" }, 31 | } 32 | 33 | for section in *opt.section 34 | items = [i for i in *opt["item"..section]] 35 | value = table.concat items, "\n" 36 | row = conf_dlg[#conf_dlg].y + conf_dlg[#conf_dlg].height 37 | conf_dlg[#conf_dlg+1] = { x: 0, y: row, width: 1, height: 1, class: "label", label: "Pre-made reports for #{section}", } 38 | conf_dlg[#conf_dlg+1] = { x: 0, y: row+1, width: 15, height: math.min(#items+2, 5), class: "textbox", value: value, name: "item"..section } 39 | 40 | buttons = { "Save", "Reset", "Cancel" } 41 | pressed, result = aegisub.dialog.display(conf_dlg, buttons) 42 | switch pressed 43 | when "Cancel" then aegisub.cancel! 44 | when "Save" 45 | section_tbl = {} 46 | for s in result["section"]\gmatch("[^\n]+") 47 | table.insert section_tbl, s 48 | 49 | opt["item"..s] = {} 50 | continue unless result["item"..s] 51 | for item in result["item"..s]\gmatch("[^\n]+") 52 | table.insert opt["item"..s], item 53 | 54 | opt.section = section_tbl 55 | config\write() 56 | when "Reset" 57 | opt.section = default_config.section 58 | config\write() 59 | 60 | 61 | clear_notes = (subs, sel) -> 62 | to_delete = {} 63 | for i = 1, #subs do 64 | continue unless subs[i].class == "dialogue" 65 | line = subs[i] 66 | continue unless line.text\match "{%*%[.*%*}" 67 | line.text = line.text\gsub "{%*%[.*%*}", "" 68 | line.effect = line.effect\gsub "%[QC-[^%]]+%]", "" 69 | if line.text == "" then table.insert(to_delete, i) 70 | subs[i] = line 71 | subs.delete(to_delete) 72 | 73 | 74 | create_gui = (opt) -> 75 | dlg = {} 76 | for index, item in ipairs opt.section 77 | dropdownitems = [item for item in *opt["item"..item]] 78 | dlg[#dlg+1] = {x: index-1, y: 0, class: "checkbox", label: item, name: item} 79 | dlg[#dlg+1] = {x: index-1, y: 1, class: "dropdown", items: dropdownitems, name: "item"..item, value: nil} 80 | 81 | dlg[#dlg+1] = {x: 0, y: 2, width: 2, height: 1, class: "label", label: "Write you notes below:"} 82 | dlg[#dlg+1] = {x: 16, y: 2, width: 2, height: 1, class: "checkbox", label: "Use video frame", name: "use_video", value: opt.use_video} 83 | dlg[#dlg+1] = {x: 0, y: 3, class: "textbox", name: "note", value: "", width: 18, height: 10} 84 | 85 | return dlg 86 | 87 | 88 | -- This one uses video frame position to add notes 89 | -- If the current frame has a line, then then the note will be added to that line 90 | -- If the current frame has no line, then a new line with note will be inserted with current frame's time 91 | useVideo = (subs, header, note) -> 92 | video_frame = aegisub.project_properties().video_position 93 | 94 | unless video_frame 95 | logger\log "Video is not loaded. Adding note to current selected line." 96 | return nil 97 | else 98 | time_frame = aegisub.ms_from_frame(video_frame) 99 | 100 | for i=1, #subs 101 | continue unless subs[i].class == "dialogue" 102 | line = subs[i] 103 | if time_frame > line.start_time and time_frame < line.end_time 104 | return i, {i} 105 | 106 | for i=1, #subs 107 | continue unless subs[i].class == "dialogue" 108 | line = subs[i] 109 | if line.start_time > time_frame 110 | line.text = "{*#{note}*}" 111 | line.start_time = time_frame 112 | line.end_time = time_frame 113 | line.effect = "[QC-#{header}]" 114 | subs[-i] = line 115 | return false, {i} 116 | 117 | 118 | writeQC = (subs, sel, act) -> 119 | config\load() 120 | opt = config.c 121 | 122 | dlg = create_gui opt 123 | btn, res = aegisub.dialog.display dlg, {"Add Note", "Cancel"}, {"ok": "Add Note", "cancel": "Cancel"} 124 | 125 | opt.use_video = res["use_video"] 126 | config\write() 127 | 128 | if btn 129 | header = "Note" 130 | template = "" 131 | for section in *opt.section 132 | if res[section] 133 | header = section 134 | template = res["item"..section] 135 | 136 | note = res["note"] 137 | return if note == "" and template == "" 138 | note = "#{template} #{note}" 139 | 140 | note = note\gsub("\n", "\\N")\gsub("{", "[")\gsub("}", "]") 141 | note = "[#{header}] #{note}" 142 | 143 | -- If video_frame is chosen 144 | local new_index 145 | local new_sel 146 | if res["use_video"] 147 | new_index, new_sel = useVideo(subs, header, note) 148 | return new_sel unless new_index 149 | 150 | new_index or= act 151 | line = subs[new_index] 152 | line.text ..= "{*#{note}*}" 153 | line.effect ..= "[QC-#{header}]" 154 | subs[new_index] = line 155 | 156 | return new_sel 157 | 158 | 159 | ms2timecode = (num) -> 160 | hh = math.floor((num / (60 * 60 * 1000)) % 24) 161 | mm = math.floor((num / (60 * 1000)) % 60) 162 | ss = math.floor((num / 1000) % 60) 163 | if show_hour 164 | return string.format("%01d:%02d:%02d", hh, mm, ss) 165 | else 166 | return string.format("%02d:%02d", mm, ss) 167 | 168 | 169 | generate_QC = (subs, sel) -> 170 | maxTime = 0 171 | for i = 1, #subs 172 | continue unless subs[i].class == "dialogue" 173 | maxTime = math.max(subs[i].start_time, maxTime) 174 | show_hour = math.floor((maxTime/(60*60*1000))%24) > 0 175 | 176 | report = {} 177 | for i = 1, #subs do 178 | continue unless subs[i].class == "dialogue" 179 | line = subs[i] 180 | for qc in line.text\gmatch "{%*([^%*]+)%*}" 181 | section_header = qc\match "^%[([^%]]+)%].*" 182 | note = qc\gsub("#{section_header}", "")\gsub("[%[%]]", "")\gsub("^%s+", "")\gsub("\\N", "\n") 183 | note = ms2timecode(line.start_time).." - "..note 184 | report[section_header] or= {} 185 | table.insert(report[section_header], note) 186 | 187 | for k, v in pairs report 188 | logger\log "[#{k}]" 189 | for note in *v 190 | logger\log note 191 | logger\log " " 192 | 193 | 194 | depctrl\registerMacros({ 195 | { "Write QC", script_description, writeQC }, 196 | { "Generate QC Report", script_description, generate_QC }, 197 | { "Config", "Configuration for script", config_setup }, 198 | { "Clear Notes", "Clear the notes", clear_notes }, 199 | }) 200 | -------------------------------------------------------------------------------- /macros/phos.RemoveTags.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Remove Tags" 2 | export script_description = "Dynamically remove tags based on selection" 3 | export script_version = "1.0.3" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.RemoveTags" 6 | 7 | 8 | DependencyControl = require "l0.DependencyControl" 9 | depctrl = DependencyControl{ 10 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 11 | { 12 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 13 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 14 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 15 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 16 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 17 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 18 | {"phos.AssfPlus", version: "1.0.4", url: "https://github.com/PhosCity/Aegisub-Scripts", 19 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 20 | } 21 | } 22 | LineCollection, ASS, Functional, AssfPlus = depctrl\requireModules! 23 | {:list} = Functional 24 | 25 | 26 | collectTags = (lines) -> 27 | 28 | collection = AssfPlus.lineCollection.collectTags lines, true 29 | 30 | with collection 31 | 32 | .buttons = {"Remove Tags", "Remove All", "Cancel"} 33 | 34 | .removeGroup = {color: false, alpha: false, rotation: false, scale: false, perspective: false, inline_except_last: .multiple_inline_tags} 35 | 36 | .removeGroupTags = { 37 | color: {"color1", "color2", "color3", "color4"} 38 | alpha: {"alpha", "alpha1", "alpha2", "alpha3", "alpha4"} 39 | rotation: {"angle", "angle_x", "angle_y"} 40 | scale: {"fontsize", "scale_x", "scale_y"} 41 | perspective: {"angle", "angle_x", "angle_y", "shear_x", "shear_y"} 42 | } 43 | 44 | .hint = { 45 | color: "c, 1c, 2c, 3c, 4c" 46 | alpha: "alpha, 1a, 2a, 3a, 4a" 47 | rotation: "frz, frx, fry" 48 | scale: "fs, fscx, fscy" 49 | perspective: "frz, frx, fry, fax, fay, org" 50 | inline_except_last: "Remove all inline tags except the last one" 51 | start_tag: "Remove from start tags only" 52 | inline_tags: "Remove from inline tags only" 53 | transform: "Remove from transform only" 54 | invert: "Remove all except selected" 55 | } 56 | 57 | .tagTypes.invert = true 58 | 59 | for tag in *collection.tagList 60 | with collection.removeGroup 61 | if tag\match "color" 62 | .color = true 63 | elseif tag\match "alpha" 64 | .alpha = true 65 | elseif tag\match "angle" 66 | .rotation = true 67 | .perspective = true 68 | elseif tag\match "shear" or tag == "origin" 69 | .perspective = true 70 | elseif tag\match "scale" or tag == "fontsize" 71 | .scale = true 72 | 73 | collection 74 | 75 | createGui = (collection) -> 76 | 77 | y, dialog = 0, {} 78 | for key, value in pairs collection.removeGroup 79 | continue unless value 80 | label = ("Remove all #{key}")\gsub "_", " " 81 | dialog[#dialog+1]= {x: 0, y: y, class: "checkbox", label: label, name: "group_"..key, hint: collection.hint[key]} 82 | y += 1 83 | 84 | startX = 0 85 | if #dialog > 0 86 | table.insert collection.buttons, 1, "Remove Group" 87 | startX = 1 88 | x = startX 89 | 90 | for key in *[ item for item in *{"start_tag", "inline_tags", "transforms", "invert"} when collection.tagTypes[item]] -- Manually sorting since lua doesn't loop through table in order 91 | label = key\gsub("^%l", string.upper)\gsub("_%l", string.upper)\gsub("_", " ") 92 | dialog[#dialog+1]= {x: x, y: 0, class: "checkbox", label: label, name: key, hint: collection.hint[key]} 93 | x += 1 94 | 95 | column = math.max math.ceil(math.sqrt #collection.tagList), x 96 | 97 | count = 0 98 | for y = 1, column 99 | for x = startX, column + startX - 1 100 | count += 1 101 | break if count > #collection.tagList 102 | label = (ASS.toFriendlyName[collection.tagList[count]])\gsub("\\1c%s+", "")\gsub("\\", "") 103 | dialog[#dialog+1] = {x: x, y: y, class: "checkbox", label: label, name: collection.tagList[count]} 104 | 105 | btn, res = aegisub.dialog.display dialog, collection.buttons, {"cancel": "Cancel"} 106 | aegisub.cancel! unless btn 107 | 108 | btn, res 109 | 110 | main = (sub, sel) -> 111 | 112 | lines = LineCollection sub, sel 113 | return if #lines.lines == 0 114 | 115 | collection = collectTags lines 116 | 117 | btn, res = createGui collection 118 | 119 | lines\runCallback (lines, line, i) -> 120 | 121 | AssfPlus._util.checkCancellation! 122 | AssfPlus._util.progress "Removing tags", i, #lines.lines 123 | 124 | data = ASS\parse line 125 | tagSectionCount = data\getSectionCount ASS.Section.Tag 126 | transforms = data\getTags "transform" 127 | switch btn 128 | 129 | when "Remove All" 130 | if res.start_tag 131 | data\removeTags _, 1, 1 132 | elseif res.inline_tags 133 | data\removeTags _, 2, tagSectionCount 134 | else 135 | data\stripTags! 136 | 137 | when "Remove Group" 138 | 139 | if res["group_inline_except_last"] 140 | data\removeTags _, (collection.tagTypes.start_tag and 2 or 1), tagSectionCount - 1 141 | 142 | else 143 | start, end_, tagsToDelete = 1, tagSectionCount, {} 144 | end_ = 1 if res.start_tag 145 | start = 2 if res.inline_tags 146 | for key, value in pairs collection.removeGroupTags 147 | continue unless res["group_" .. key] 148 | tagsToDelete = list.join tagsToDelete, value 149 | tagsToDelete = list.diff ASS.tagSortOrder, tagsToDelete if res.invert 150 | data\removeTags tagsToDelete, start, end_ 151 | 152 | when "Remove Tags" 153 | 154 | local tagsToDelete 155 | if res.invert 156 | tagsToDelete = [tag for tag in *collection.tagList when not res[tag]] 157 | else 158 | tagsToDelete = [tag for tag in *collection.tagList when res[tag]] 159 | 160 | if res.start_tag 161 | data\removeTags tagsToDelete, 1, 1 162 | 163 | elseif res.inline_tags 164 | data\removeTags tagsToDelete, 2, tagSectionCount 165 | 166 | elseif res.transforms 167 | for tr in *transforms 168 | tr.tags\removeTags tagsToDelete 169 | 170 | else 171 | data\removeTags tagsToDelete 172 | 173 | data\commit! 174 | line.text = line.text\gsub "{}", "" 175 | lines\replaceLines! 176 | 177 | depctrl\registerMacro main -------------------------------------------------------------------------------- /macros/phos.svg2ass.moon: -------------------------------------------------------------------------------- 1 | export script_name = "svg2ass" 2 | export script_description = "Script that uses ink2ass to convert svg files to ass lines" 3 | export script_version = "2.0.0" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.svg2ass" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 14 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 17 | {"phos.AegiGui", version: "1.0.0", url: "https://github.com/PhosCity/Aegisub-Scripts", 18 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 19 | }, 20 | } 21 | LineCollection, ASS, Functional, AegiGui = depctrl\requireModules! 22 | logger = depctrl\getLogger! 23 | {:list, :string} = Functional 24 | 25 | defaultConfig = 26 | ink2assPath: "" 27 | 28 | config = depctrl\getConfigHandler defaultConfig 29 | 30 | print_and_exit = (message) -> 31 | logger\log message 32 | aegisub.cancel! 33 | 34 | select_file = (file_name, file_type) -> 35 | pathsep = package.config\sub(1, 1) 36 | filename = aegisub.dialog.open("Select #{file_name} file", "", aegisub.decode_path("?script")..pathsep, "#{file_type} files (.#{file_type})|*.#{file_type}", false, true) 37 | aegisub.cancel! unless filename 38 | return filename 39 | 40 | 41 | configSetup = -> 42 | config.c.ink2assPath = select_file("Python","py") 43 | config\write! 44 | 45 | 46 | createGUI = -> 47 | dialog_str = " 48 | | drop, result_type, drawing::clip::iclip, drawing || 49 | | check, pasteover, Pasteover, false, Convert svg but paste shape date over selected lines | pad, 50 | 50 | " 51 | 52 | dialog, button, buttonID = AegiGui.create dialog_str, "Import:ok, Cancel:cancel" 53 | btn, res = aegisub.dialog.display(dialog, button, buttonID) 54 | aegisub.cancel! if btn == "Cancel" 55 | return res 56 | 57 | 58 | -- Surveys the selected lines. Returns the least starting time, max end time and a style among them 59 | reconnaissance = (sub, sel) -> 60 | startTime, endTime, styleList = math.huge, 0, {} 61 | for i in *sel 62 | startTime = math.min(startTime, sub[i].start_time) 63 | endTime = math.max(endTime, sub[i].end_time) 64 | styleList[#styleList + 1] = sub[i].style 65 | styleList = list.uniq styleList 66 | 67 | local style 68 | if #styleList < 2 69 | style = styleList[1] 70 | else 71 | dialog_str = " 72 | | label, Your selection has multiple styles. Please select one: | 73 | | drop, stl, #{table.concat(styleList,"::")}, #{styleList[1]} | 74 | " 75 | 76 | dialog, button, buttonID = AegiGui.create dialog_str, "Ok:ok, Cancel:cancel" 77 | btn, res = aegisub.dialog.display(dialog, button, buttonID) 78 | aegisub.cancel! if btn == "Cancel" 79 | style = res.stl 80 | return startTime, endTime, style 81 | 82 | 83 | sanitize_result = (result, res) -> 84 | match_string = "" 85 | if res.pasteover 86 | match_string = "m [%d%.%a%s%-]+" 87 | elseif res.clip 88 | match_string = "\\clip%(m [%d%.%a%s%-]+" 89 | elseif res.iclip 90 | match_string = "\\iclip%(m [%d%.%a%s%-]+" 91 | else 92 | match_string = "Dialogue: %d+,[^,]+,[^,]+,[^,]+,,%d+,%d+,%d+,,.+" 93 | 94 | result = string.split(result, "\n") 95 | sanitized_result, isInvalid = {}, false 96 | for line in *result 97 | continue if line == "" 98 | if line\match match_string 99 | table.insert sanitized_result, line 100 | continue 101 | logger\log "Invalid line: #{line}" 102 | isInvalid = true 103 | 104 | aegisub.cancel! if isInvalid 105 | return sanitized_result 106 | 107 | 108 | -- Check if svg2ass exists in the path given in config 109 | checkInk2assExists = (path) -> 110 | handle = io.open(path, "r") 111 | if handle 112 | io.close! 113 | return 114 | print_and_exit "ink2ass file not found. Please install it and try again." 115 | 116 | 117 | -- Execute the command 118 | runCommand = (command) -> 119 | handle = io.popen(command) 120 | output = handle\read("*a") 121 | success = handle\close! 122 | unless success 123 | print_and_exit "The program did not run successfully.\nHere's what you can do: Run the python code in commandline.\nOr send svg file to Phos in discord to check." 124 | return output 125 | 126 | 127 | main = (sub, sel) -> 128 | 129 | config\load! 130 | opt = config.c 131 | res = createGUI! 132 | startTime, endTime, style = reconnaissance(sub, sel) 133 | 134 | checkInk2assExists opt.ink2assPath 135 | filename = select_file("SVG", "svg") 136 | 137 | output_type = res.pasteover and "drawing" or res.clip and "clip" or res.iclip and "iclip" or "line" 138 | command = "python \"#{opt.ink2assPath}\" \"#{filename}\" --output_format=\"#{output_type}\"" 139 | result = runCommand(command) 140 | 141 | result = sanitize_result result, res 142 | 143 | lines = LineCollection sub, sel 144 | return if #lines.lines == 0 145 | 146 | -- Pastes the shape data over the selected lines while keeping the original tags 147 | if res.pasteover 148 | if #lines.lines ~= #result 149 | print_and_exit "Number of selected lines (#{#lines.lines}) is not equal to number of output lines (#{#result}). Pasteover failed." 150 | 151 | lines\runCallback ((lines, line, i) -> 152 | if res.drawing 153 | line.text = result[i] 154 | else 155 | data = ASS\parse line 156 | shape = result[i]\match "{[^}]+}(.+)" 157 | drawing = ASS.Draw.DrawingBase{str: shape} 158 | 159 | if res.clip 160 | data\replaceTags {ASS\createTag "clip_vect", drawing} 161 | elseif res.iclip 162 | data\replaceTags {ASS\createTag "iclip_vect", drawing} 163 | 164 | data\commit! 165 | ), true 166 | lines\replaceLines! 167 | return lines\getSelection! 168 | else 169 | -- Add shapes as new lines 170 | for ln in *(list.reverse result) 171 | if res.clip or res.iclip 172 | ln = "Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{#{ln}}" 173 | 174 | newLine = ASS\createLine { 175 | ln 176 | lines 177 | start_time: startTime 178 | end_time: endTime 179 | style: style 180 | } 181 | lines\addLine newLine, nil, true, sel[1] 182 | lines\insertLines! 183 | return [x for index, x in ipairs lines\getSelection! when index > #sel] 184 | 185 | 186 | depctrl\registerMacros({ 187 | {"Run", "Run the script", main}, 188 | {"Config", "Configuration for e script", configSetup} 189 | }) 190 | -------------------------------------------------------------------------------- /macros/phos.VectorGradient.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Vector Gradient" 2 | export script_description = "Magic triangles + blur gradients" 3 | export script_version = "0.0.1" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.VectorGradient" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 14 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 17 | }, 18 | } 19 | LineCollection, ASS, Functional = depctrl\requireModules! 20 | logger = depctrl\getLogger! 21 | { :math } = Functional 22 | 23 | createGUI = -> 24 | dialog = { 25 | {x: 0, y: 0, width: 1, height: 1, class: "checkbox", name: "wedge", label: "Wedge"}, 26 | {x: 0, y: 1, width: 1, height: 1, class: "checkbox", name: "ring", label: "Ring"}, 27 | {x: 0, y: 2, width: 1, height: 1, class: "checkbox", name: "star", label: "star"}, 28 | } 29 | btn, res = aegisub.dialog.display dialog, {"OK", "Cancel"}, {"ok": "OK", "cancel": "Cancel"} 30 | aegisub.cancel! unless btn 31 | res 32 | 33 | 34 | wedge = (data, clip) -> 35 | -- For 3 points, determine the point of intersection of line passing through first 2 points and a line perpendicular to it passing through 3rd point 36 | x1, y1, x2, y2, x3, y3 = unpack clip 37 | k = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / ((x2 - x1)^2 + (y2 - y1)^2) 38 | x = x1 + k * (x2 - x1) 39 | y = y1 + k * (y2 - y1) 40 | 41 | length = math.vector2.distance x1, y1, x2, y2 42 | height = math.vector2.distance x3, y3, x, y 43 | wedgeBase = 0.09009 * height 44 | shape = "m 0 0 l 0 #{wedgeBase} " 45 | local prev 46 | for i = 0, length + 10, 10 47 | if i == 0 48 | prev = i 49 | continue 50 | 51 | current = i 52 | wedgeCorner = (prev + i)/2 53 | current = length if i > length 54 | wedgeCorner = length if wedgeCorner > length 55 | prev = i 56 | 57 | shape ..= "#{wedgeCorner} #{height} #{current} #{wedgeBase} " 58 | shape ..= "#{length} 0 0 0" 59 | 60 | angle = math.atan2(y2-y1, x2-x1) 61 | angle = math.degrees(-angle) 62 | drawing = ASS.Draw.DrawingBase{str: shape} 63 | 64 | data\removeSections 2, #data.sections 65 | data\insertSections ASS.Section.Drawing {drawing} 66 | data\removeTags { "outline_x", "outline_y", "shadow_x", "shadow_y", "shear_x", "shear_y", "angle_x", "angle_y", "clip_vect"} 67 | data\replaceTags {ASS\createTag 'position', x1, y1} 68 | data\replaceTags {ASS\createTag 'origin', x1, y1} 69 | data\replaceTags {ASS\createTag 'scale_x', 100} 70 | data\replaceTags {ASS\createTag 'scale_y', 100} 71 | data\replaceTags {ASS\createTag 'outline', 0} 72 | data\replaceTags {ASS\createTag 'shadow', 0} 73 | data\replaceTags {ASS\createTag 'align', 7} 74 | data\replaceTags {ASS\createTag 'angle', angle} 75 | data 76 | 77 | 78 | radial = (data, clip, shape, width) -> 79 | -- Determing the upper left corner of the bounding box 80 | x1, y1, x2, y2 = unpack clip 81 | centerX, centerY = (x1 + x2)/2, (y1 + y2)/2 82 | diameter = math.vector2.distance x1, y1, x2, y2 83 | x = centerX - (diameter/2) 84 | y = centerY - (diameter/2) 85 | 86 | drawing = ASS.Draw.DrawingBase{str: shape} 87 | 88 | data\removeSections 2, #data.sections 89 | data\insertSections ASS.Section.Drawing {drawing} 90 | data\removeTags { "origin" ,"outline_x", "outline_y", "shadow_x", "shadow_y", "shear_x", "shear_y", "angle_x", "angle_y", "clip_vect"} 91 | data\replaceTags {ASS\createTag 'position', x, y} 92 | data\replaceTags {ASS\createTag 'scale_x', diameter*100/width} 93 | data\replaceTags {ASS\createTag 'scale_y', diameter*100/width} 94 | data\replaceTags {ASS\createTag 'outline', 0} 95 | data\replaceTags {ASS\createTag 'shadow', 0} 96 | data\replaceTags {ASS\createTag 'align', 7} 97 | data\replaceTags {ASS\createTag 'angle', 0} 98 | data 99 | 100 | 101 | main = (sub, sel) -> 102 | res = createGUI! 103 | lines = LineCollection sub, sel 104 | return if #lines.lines == 0 105 | lines\runCallback (lines, line, i) -> 106 | aegisub.cancel! if aegisub.progress.is_cancelled! 107 | data = ASS\parse line 108 | 109 | hasClip, clip = false, {} 110 | clipCount = res.wedge and 3 or 2 111 | clipTable = data\getTags "clip_vect" 112 | if #clipTable != 0 113 | hasClip = true 114 | for index, cnt in ipairs clipTable[1].contours[1].commands -- Is this the best way to loop through co-ordinate? 115 | break if index == clipCount + 1 116 | x, y = cnt\get! 117 | table.insert clip, x 118 | table.insert clip, y 119 | if hasClip and #clip != 2 * clipCount 120 | logger\warn "Clip found in line #{line.humanizedNumber} but the clip has less than #{clipCount} points.\nSkipping this line." 121 | hasClip = false 122 | return unless hasClip 123 | 124 | if res.wedge 125 | data = wedge data, clip 126 | elseif res.ring 127 | shape = "m 93.7 0.4 b 42 0.4 0 42.4 0 94.1 0 145.9 42 187.9 93.7 187.9 145.5 187.9 187.5 145.9 187.5 94.1 187.5 42.4 145.5 0.4 93.7 0.4 m 93.7 1.4 b 145 1.4 186.5 42.9 186.5 94.1 186.5 145.3 145 186.9 93.7 186.9 42.5 186.9 1 145.3 1 94.1 1 42.9 42.5 1.4 93.7 1.4 m 93.7 9.7 b 47.1 9.7 9.3 47.5 9.3 94.1 9.3 140.7 47.1 178.5 93.7 178.5 140.3 178.5 178.1 140.7 178.1 94.1 178.1 47.5 140.3 9.7 93.7 9.7 m 93.7 11.7 b 139.3 11.7 176.2 48.6 176.2 94.1 176.2 139.7 139.3 176.6 93.7 176.6 48.2 176.6 11.3 139.7 11.3 94.1 11.3 48.6 48.2 11.7 93.7 11.7 m 93.7 19 b 52.3 19 18.6 52.7 18.6 94.1 18.6 135.6 52.3 169.2 93.7 169.2 135.2 169.2 168.8 135.6 168.8 94.1 168.8 52.7 135.2 19 93.7 19 m 93.7 22 b 133.6 22 165.9 54.3 165.9 94.1 165.9 134 133.6 166.3 93.7 166.3 53.9 166.3 21.6 134 21.6 94.1 21.6 54.3 53.9 22 93.7 22 m 93.7 28.4 b 57.4 28.4 28 57.8 28 94.1 28 130.4 57.4 159.9 93.7 159.9 130 159.9 159.5 130.4 159.5 94.1 159.5 57.8 130 28.4 93.7 28.4 m 93.7 32.3 b 127.9 32.3 155.6 59.9 155.6 94.1 155.6 128.3 127.9 156 93.7 156 59.5 156 31.9 128.3 31.9 94.1 31.9 59.9 59.5 32.3 93.7 32.3 m 93.7 37.7 b 62.6 37.7 37.3 63 37.3 94.1 37.3 125.3 62.6 150.6 93.7 150.6 124.9 150.6 150.2 125.3 150.2 94.1 150.2 63 124.9 37.7 93.7 37.7 m 93.7 42.6 b 122.2 42.6 145.3 65.6 145.3 94.1 145.3 122.6 122.2 145.6 93.7 145.6 65.2 145.6 42.2 122.6 42.2 94.1 42.2 65.6 65.2 42.6 93.7 42.6 m 93.7 47 b 67.7 47 46.6 68.1 46.6 94.1 46.6 120.1 67.7 141.2 93.7 141.2 119.7 141.2 140.8 120.1 140.8 94.1 140.8 68.1 119.7 47 93.7 47 m 93.7 52.9 b 116.5 52.9 134.9 71.3 134.9 94.1 134.9 116.9 116.5 135.3 93.7 135.3 70.9 135.3 52.5 116.9 52.5 94.1 52.5 71.3 70.9 52.9 93.7 52.9 m 93.7 56.4 b 72.9 56.4 56 73.3 56 94.1 56 114.9 72.9 131.9 93.7 131.9 114.5 131.9 131.5 114.9 131.5 94.1 131.5 73.3 114.5 56.4 93.7 56.4 m 93.7 63.2 b 110.8 63.2 124.6 77 124.6 94.1 124.6 111.2 110.8 125 93.7 125 76.6 125 62.8 111.2 62.8 94.1 62.8 77 76.6 63.2 93.7 63.2 m 93.7 65.7 b 78 65.7 65.3 78.4 65.3 94.1 65.3 109.8 78 122.6 93.7 122.6 109.4 122.6 122.2 109.8 122.2 94.1 122.2 78.4 109.4 65.7 93.7 65.7 m 93.7 73.5 b 105.2 73.5 114.3 82.7 114.3 94.1 114.3 105.5 105.2 114.7 93.7 114.7 82.3 114.7 73.1 105.5 73.1 94.1 73.1 82.7 82.3 73.5 93.7 73.5 m 93.7 75 b 83.2 75 74.6 83.6 74.6 94.1 74.6 104.6 83.2 113.3 93.7 113.3 104.2 113.3 112.9 104.6 112.9 94.1 112.9 83.6 104.2 75 93.7 75 m 93.7 83.8 b 99.5 83.8 104 88.4 104 94.1 104 99.9 99.5 104.4 93.7 104.4 88 104.4 83.4 99.9 83.4 94.1 83.4 88.4 88 83.8 93.7 83.8 m 93.7 84.3 l 93.7 84.3 89.8 85.1 86.6 87.4 84.5 90.8 83.9 94.1 83.9 94.1 84.7 98 87 101.3 90.4 103.3 93.7 103.9 93.7 103.9 97.6 103.1 100.9 100.9 102.9 97.5 103.5 94.1 103.5 94.1 102.7 90.2 100.5 87 97.1 84.9 93.7 84.3" 128 | data = radial data, clip, shape, 188 129 | elseif res.star 130 | shape = "m 89.2 -0.1 l 91.9 71 116.5 4.3 97.2 72.8 141.2 16.9 101.7 76 160.8 36.5 105 80.5 173.4 61.2 106.7 85.8 177.8 88.5 106.7 91.3 173.4 115.9 105 96.6 160.8 140.6 101.7 101.1 141.2 160.2 97.2 104.3 116.5 172.8 91.9 106 89.2 177.1 86.4 106 61.8 172.8 81.1 104.3 37.1 160.2 76.6 101.1 17.5 140.6 73.4 96.6 4.9 115.9 71.7 91.3 0.6 88.5 71.7 85.8 4.9 61.2 73.4 80.5 17.5 36.5 76.6 76 37.1 16.9 81.1 72.8 61.8 4.3 86.4 71 89.2 -0.1" 131 | data = radial data, clip, shape, 178 132 | data\commit! 133 | lines\replaceLines! 134 | 135 | depctrl\registerMacro main 136 | -------------------------------------------------------------------------------- /macros/phos.AutoFade.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Auto Fade" 2 | export script_description = "Automatically determine fade in and fade out" 3 | export script_version = "1.1.2" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.AutoFade" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"a-mo.DataWrapper", version: "1.0.2", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 14 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 15 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 16 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 17 | "aegisub.clipboard" 18 | }, 19 | } 20 | LineCollection, DataWrapper, ASS, clipboard = depctrl\requireModules! 21 | logger = depctrl\getLogger! 22 | 23 | 24 | windowAssertError = ( condition, errorMessage ) -> 25 | if not condition 26 | logger\log errorMessage 27 | aegisub.cancel! 28 | 29 | 30 | createGUI = (coordinateValue, dataLabel, data, processType) -> 31 | dialog = { 32 | {x: 0, y: 0, width: 25, height: 1, class: "label", label: dataLabel or "Paste data or enter a filepath."}, 33 | {x: 0, y: 1, width: 25, height: 5, class: "textbox", hint: "Paste data or the path to a file containing it. No quotes or escapes.", name: "data", value: data or "" } 34 | {x: 0, y: 6, width: 25, height: 1, class: "dropdown", name: "processType", value: processType or "Single Co-ordinate", items: {"Single Co-ordinate", "Tracking Data"} }, 35 | {x: 0, y: 7, width: 1, height: 1, class: "label", label: "Co-ordinate"}, 36 | {x: 1, y: 7, width: 24, height: 1, class: "edit", name: "coordinate", value: coordinateValue}, 37 | } 38 | btn, res = aegisub.dialog.display dialog, {"Fade &in", "Fade &out", "&Both", "&Cancel"} 39 | if btn == nil or btn == "&Cancel" 40 | aegisub.cancel! 41 | else 42 | return res, btn 43 | 44 | 45 | getColor = (curFrame, x, y) -> 46 | frame = aegisub.get_frame(curFrame, false) 47 | color = frame\getPixelFormatted(x, y) 48 | color 49 | 50 | 51 | extractRGB = (color) -> 52 | b, g, r = color\match "&H(..)(..)(..)&" 53 | tonumber(b, 16), tonumber(g, 16), tonumber(r, 16) 54 | 55 | 56 | euclideanDistance = (color1, color2) -> 57 | b1, g1, r1 = extractRGB(color1) 58 | b2, g2, r2 = extractRGB(color2) 59 | math.sqrt((b1-b2)^2 + (g1-g2)^2 + (r1-r2)^2) 60 | 61 | 62 | determineFadeTime = (fadeType, startFrame, endFrame, targetColor, pos) -> 63 | local fadeTime 64 | for i = startFrame, endFrame 65 | color = getColor(i, pos["x"][i], pos["y"][i]) 66 | dist = euclideanDistance(color, targetColor) 67 | if fadeType == "Fade in" and dist < 5 68 | fadeTime = math.floor((aegisub.ms_from_frame(i+1)+ aegisub.ms_from_frame(i))/2) 69 | break 70 | elseif fadeType == "Fade out" and dist > 5 71 | fadeTime = math.floor((aegisub.ms_from_frame(i-1)+ aegisub.ms_from_frame(i))/2) 72 | break 73 | windowAssertError fadeTime, "#{fadeType} time could not be determined." 74 | fadeTime 75 | 76 | 77 | main = (sub, sel) -> 78 | windowAssertError aegisub.get_frame, "You are using unsupported Aegisub.\nPlease use arch1t3cht's Aegisub for this script." 79 | windowAssertError aegisub.project_properties!.video_file != "", "No video open. Exiting." 80 | fadeLimit = (aegisub.ms_from_frame(2)-aegisub.ms_from_frame(1))/2 -- Fade time below this can be negleted. 81 | trackingData = DataWrapper! 82 | 83 | lines = LineCollection sub, sel 84 | windowAssertError #lines.lines == 1, "Because of how this script works, it can only be run in one line at a time." 85 | lines\runCallback (_, line) -> 86 | local removeClip, fadein, fadeout, btn, dataLabel, processType 87 | totalFrames = line.endFrame - line.startFrame 88 | pos = {x: {}, y: {}} 89 | 90 | currentFrame = aegisub.project_properties!.video_position 91 | windowAssertError currentFrame >= line.startFrame, "Your current video position is before the start time of the line." 92 | windowAssertError currentFrame <= line.endFrame, "Your current video position is after the end time of the line." 93 | 94 | -- Try to see if there is a single point clip in the line 95 | xCord, yCord = line.text\match "\\i?clip%(m ([%d.]+) ([%d.]+)%s*%)" 96 | if xCord 97 | removeClip = true 98 | else -- Since there is no single point clip, try to see if there is coordinate in clipboard 99 | xCord, yCord = (clipboard.get! or "")\match "([%d.]+),([%d.]+)" 100 | 101 | -- GUI, Tracking Data, Relative Frames 102 | while true 103 | rawInputData = clipboard.get! or "" 104 | parsed = trackingData\bestEffortParsingAttempt rawInputData, lines.meta.PlayResX, lines.meta.PlayResY 105 | res, btn = createGUI (xCord and "#{xCord},#{yCord}" or ""), dataLabel, (parsed and rawInputData or nil), processType 106 | 107 | -- Co-ordinate validation 108 | xCord, yCord = res.coordinate\match "([%d.]+),([%d.]+)" 109 | if not xCord and not yCord 110 | windowAssertError false, "Invalid co-ordinate. The format of the co-ordinate is x,y" 111 | 112 | -- Bail out early if we don't need to deal with tracking data 113 | if res.processType == "Single Co-ordinate" 114 | pos["x"] = {i, xCord for i = line.startFrame, line.endFrame - 1} 115 | pos["y"] = {i, yCord for i = line.startFrame, line.endFrame - 1} 116 | break 117 | else 118 | processType = res.processType 119 | 120 | if res.data == "" 121 | dataLabel = "As far as I can tell, you've forgotten to give me any motion data." 122 | continue 123 | 124 | unless trackingData\bestEffortParsingAttempt res.data, lines.meta.PlayResX, lines.meta.PlayResY 125 | dataLabel = "You put something in the data box\nbut it is wrong in ways I can't imagine." 126 | continue 127 | 128 | unless trackingData.dataObject\checkLength totalFrames 129 | dataLabel = "The length of your data (#{trackingData.dataObject.length} frames) doesn't match\nthe length of your lines (#{totalFrames} frames)." 130 | continue 131 | 132 | -- Add the current frame as reference frame to get relative positions 133 | trackingData.dataObject\addReferenceFrame currentFrame - lines.startFrame + 1 134 | with trackingData.dataObject 135 | count = 1 136 | for i = line.startFrame, line.endFrame - 1 137 | pos["x"][i] = xCord + (.xPosition[count] - .xStartPosition) 138 | pos["y"][i] = yCord + (.yPosition[count] - .yStartPosition) 139 | count += 1 140 | break 141 | 142 | targetColor = getColor(currentFrame, xCord, yCord) 143 | if btn == "Fade &in" or btn == "&Both" 144 | fadeinTime = determineFadeTime("Fade in", line.startFrame, currentFrame, targetColor, pos) 145 | fadein = fadeinTime - line.start_time 146 | fadein = 0 if fadein < fadeLimit 147 | 148 | if btn == "Fade &out" or btn == "&Both" 149 | -- Speed up calculation of fade out by skipping having to step through each frame 150 | while true 151 | fr = math.floor((currentFrame + line.endFrame) / 2) 152 | color = getColor(fr, pos["x"][fr], pos["y"][fr]) 153 | if euclideanDistance(color, targetColor) < 5 154 | currentFrame = fr 155 | else 156 | break 157 | break if line.endFrame - currentFrame < 10 158 | fadeoutTime = determineFadeTime("Fade out", currentFrame, line.endFrame, targetColor, pos) 159 | fadeout = line.end_time - fadeoutTime 160 | fadeout = 0 if fadeout < fadeLimit 161 | 162 | data = ASS\parse line 163 | -- If the line already has fad tag and you only choose to determine fade in or fade out, the other time will remain unchanged. 164 | fad = data\getTags "fade_simple" 165 | if #fad != 0 and btn != "&Both" 166 | t1, t2 = fad[1]\getTagParams! 167 | fadein = t1 if btn == "Fade &out" 168 | fadeout = t2 if btn == "Fade &in" 169 | 170 | if fadein or fadeout 171 | fadein or= 0 172 | fadeout or= 0 173 | data\removeTags {"clip_vect", "iclip_vect"} if removeClip 174 | if fadein != 0 or fadeout != 0 175 | data\replaceTags {ASS\createTag "fade_simple", fadein, fadeout} 176 | data\commit! 177 | else 178 | windowAssertError false, "Neither fade in nor fade out could be determined." 179 | lines\replaceLines! 180 | 181 | depctrl\registerMacro main 182 | -------------------------------------------------------------------------------- /docs/examples/AegiGUI.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Test AegiGui" 2 | export script_description = "Test AegiGui" 3 | export script_version = "0.0.1" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.TestAegiGui" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"phos.AegiGui", version: "0.0.1", url: "https://github.com/PhosCity/Aegisub-Scripts", 12 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 13 | }, 14 | } 15 | AegiGui = depctrl\requireModules! 16 | 17 | main = (sub, sel) -> 18 | str = " 19 | | label, Label 1 | label, Label 2 | label, Label 3 | 20 | | label, Label of width 3 | | | 21 | | | label, Label of width 2 | | 22 | | label, Label of width 1 | null | | 23 | " 24 | 25 | gui = AegiGui.create str 26 | aegisub.dialog.display gui 27 | 28 | ----------------------------------------------------------------------------------------------- 29 | 30 | lblStr = "This is a string in variable." 31 | str = " 32 | | label, #{lblStr} | 33 | | label, [[Escaping illegal characters like , or |]] | 34 | " 35 | btn = "Button1, Button2" 36 | AegiGui.open str, btn 37 | 38 | ----------------------------------------------------------------------------------------------- 39 | 40 | str = " 41 | | label, Basic intedit class | 42 | | intedit, int1, 10 | 43 | | label, With alias | 44 | | int, int2, 10 | 45 | | label, with minimum value | 46 | | int, int3, 10, 0 | 47 | | label, with maximum value | 48 | | int, int4, 10, _, 100 | 49 | | label, with both max and min value | 50 | | int, int5, 10, 0, 100 | 51 | | label, with hint | 52 | | int, int6, 10, _, _, This is a hint | 53 | " 54 | btn = "Button1:ok, Button2:cancel" 55 | AegiGui.open str, btn 56 | 57 | ----------------------------------------------------------------------------------------------- 58 | 59 | str = " 60 | | label, Basic floatedit class | 61 | | floatedit, float1, 1.5 | 62 | | label, With alias | 63 | | float, float2, 1.5 | 64 | | label, with minimum value | 65 | | float, float3, 1.5, 0 | 66 | | label, with maximum value | 67 | | float, float4, 1.5, _, 10.5 | 68 | | label, with both max and min value | 69 | | float, float5, 1.5, 0, 10.5 | 70 | | label, [[with both max,min and step value]] | 71 | | float, float6, 1.5, 0, 10.5, 0.5 | 72 | | label, with hint | 73 | | float, float7, 1.5, _, _, _, This is a hint | 74 | " 75 | AegiGui.open str 76 | 77 | ----------------------------------------------------------------------------------------------- 78 | 79 | str = " 80 | | label, Basic checkbox class | 81 | | checkbox, chk1, Checkbox 1 | 82 | | label, With alias | 83 | | check, chk2, Checkbox 2 | 84 | | label, Force true value | 85 | | check, chk3, Checkbox 3, true | 86 | | label, Force false value | 87 | | check, chk4, Checkbox 4, false | 88 | | label, With hints | 89 | | check, chk5, Checkbox 5, _, This is a hint. | 90 | " 91 | AegiGui.open str 92 | 93 | ----------------------------------------------------------------------------------------------- 94 | 95 | str = " 96 | | label, Basic dropdown class | 97 | | dropdown, drp1, 1::2::1, 1 | 98 | | label, With alias | 99 | | drop, drp2, item1::item2 | 100 | " 101 | AegiGui.open str 102 | 103 | 104 | alfa = {"00","20","30","40","50","60","70","80","90","A0","B0","C0","D0","F0"} 105 | drop_alfa = table.concat(alfa,"::")..","..alfa[8] 106 | 107 | layer = {"-5","-4","-3","-2","-1","+1","+2","+3","+4","+5"} 108 | drop_layer = table.concat(layer,"::")..","..layer[6] 109 | 110 | ----------------------------------------------------------------------------------------------- 111 | 112 | str = " 113 | | label, alpha | drop, alpha, #{drop_alfa} | 114 | | label, layer | drop, layer, #{drop_layer} | 115 | " 116 | AegiGui.open str 117 | 118 | textboxValue = "My name is Yoshikage Kira. I'm 33 years old. My house is in the northeast section of Morioh, where all the villas are, and I am not married. I work as an employee for the Kame Yu department stores, and I get home every day by 8 PM at the latest. I don't smoke, but I occasionally drink. I'm in bed by 11 PM, and make sure I get eight hours of sleep, no matter what. After having a glass of warm milk and doing about twenty minutes of stretches before going to bed, I usually have no problems sleeping until morning. Just like a baby, I wake up without any fatigue or stress in the morning. I was told there were no issues at my last check-up. I'm trying to explain that I'm a person who wishes to live a very quiet life. I take care not to trouble myself with any enemies, like winning and losing, that would cause me to lose sleep at night. That is how I deal with society, and I know that is what brings me happiness. Although, if I were to fight I wouldn't lose to anyone." 119 | 120 | ----------------------------------------------------------------------------------------------- 121 | 122 | str = " 123 | | label, Textbox with value | null | label, Using variable as value | 124 | | text, txt1, 5, This is some text | pad,5 |text, txt2, 5, [[#{textboxValue}]] | 125 | null 126 | null 127 | null 128 | null 129 | " 130 | AegiGui.open str 131 | 132 | ----------------------------------------------------------------------------------------------- 133 | 134 | str = " 135 | | edit, edit1 | 136 | " 137 | AegiGui.open str 138 | 139 | ----------------------------------------------------------------------------------------------- 140 | 141 | str = " 142 | | label, It's width also depends on other elements of row| 143 | | edit, edit1, Value inside edit box | 144 | " 145 | AegiGui.open str 146 | 147 | ----------------------------------------------------------------------------------------------- 148 | 149 | str = " 150 | | label, Empty coloralpha value is valid | coloralpha, cl1 | 151 | | label, ABGR value can be used like this | coloralpha, cl2, &HAA0405F7& | 152 | " 153 | AegiGui.open str 154 | 155 | ----------------------------------------------------------------------------------------------- 156 | 157 | str = " 158 | | label,-Config----- | | | 159 | | check,commentconfig,Comments around fold | | | 160 | null 161 | | label,-Selection----- | label,-Deletion----- | | 162 | | check,select,Select current fold | check,delete,Delte current fold | | 163 | null 164 | | label,-Comment---- | | | 165 | | check,comment,Comment | check,uncomment,Uncomment | | 166 | null 167 | | label,-Cut-Copy-Paste----- | | | 168 | | check,cut,Cut curent fold | check,copy,Copy current fold | check,paste,Paste current fold | 169 | null 170 | | label,-Others----- | | | 171 | | check,create,Create a new named fold around selected lines | | | 172 | " 173 | AegiGui.open str 174 | 175 | ----------------------------------------------------------------------------------------------- 176 | 177 | str = " 178 | | text,data,5,[[have ass, will typeset]] || | | | | | | 179 | null 180 | null 181 | null 182 | null 183 | | check,drawing,drawing | pad,10 | check,clip,clip | pad,10 | check,iclip,iclip | pad,10 | check,pasteover,pasteover | pad,10 | 184 | " 185 | AegiGui.open str 186 | 187 | depctrl\registerMacro main 188 | -------------------------------------------------------------------------------- /macros/phos.ChromaticAbberation.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Chromatic Abberation" 2 | export script_description = "Add chromatic abberation to shape and text." 3 | export script_version = "1.0.4" 4 | export script_author = "PhosCity" 5 | export script_namespace = "phos.ChromaticAbberation" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 14 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | {"phos.AssfPlus", version: "1.0.2", url: "https://github.com/PhosCity/Aegisub-Scripts", 16 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 17 | {"phos.AegiGui", version: "1.0.0", url: "https://github.com/PhosCity/Aegisub-Scripts", 18 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 19 | "aegisub.util" 20 | } 21 | } 22 | LineCollection, ASS, AssfPlus, AegiGui, util = depctrl\requireModules! 23 | 24 | globalGUIResult = {} 25 | createGUI = -> 26 | str = " 27 | | label, x Offset | float,xOffset, 2 | pad, 10 | label, Color 1 | color, color1, &H00FFFF& | 28 | | label, y Offset | float,yOffset, 2 | null | label, Color 2 | color, color2, &HFF00FF& | 29 | | check, keepBaseColor, Keep Original Color | | | label, Color 3 | color, color3, &HFFFF00& | 30 | | check, textToShape, Convert text to shape | | | | | 31 | " 32 | 33 | dialog, button, buttonID = AegiGui.create str, "Apply:ok, Revert, Reset GUI, Cancel:cancel" 34 | for index, item in pairs dialog 35 | continue unless item.name 36 | continue unless globalGUIResult[item.name] 37 | if item.text 38 | item.text = globalGUIResult[item.name] 39 | else 40 | item.value = globalGUIResult[item.name] 41 | 42 | btn, res = aegisub.dialog.display(dialog, button, buttonID) 43 | globalGUIResult = res 44 | aegisub.cancel! unless btn 45 | 46 | if btn == "Reset GUI" 47 | globalGUIResult = {} 48 | res, btn = createGUI! 49 | return res, btn 50 | 51 | res, btn 52 | 53 | 54 | mixColors = (res, baseColor) -> 55 | rgb_to_cmy = (r, g, b) -> 56 | 1 - r / 255, 1 - g / 255, 1 - b / 255 57 | 58 | cmy_to_rgb = (c, m, y) -> 59 | 255 * (1 - c), 255 * (1 - m), 255 * (1 - y) 60 | 61 | mix_cmy = (c1, m1, y1, c2, m2, y2) -> 62 | math.max(c1, c2), math.max(m1, m2), math.max(y1, y2) 63 | 64 | mix_rgb_colors = (col1, col2) -> 65 | c1, m1, y1 = rgb_to_cmy(table.unpack(col1)) 66 | c2, m2, y2 = rgb_to_cmy(table.unpack(col2)) 67 | c_result, m_result, y_result = mix_cmy(c1, m1, y1, c2, m2, y2) 68 | cmy_to_rgb(c_result, m_result, y_result) 69 | 70 | local color1, color2, color3 71 | if baseColor 72 | c_result, m_result, y_result = rgb_to_cmy(table.unpack(baseColor)) 73 | 74 | c1, m1, y1 = 0, 0, y_result 75 | c2, m2, y2 = 0, m_result, 0 76 | c3, m3, y3 = c_result, 0, 0 77 | 78 | r1, g1, b1 = cmy_to_rgb(c1, m1, y1) 79 | r2, g2, b2 = cmy_to_rgb(c2, m2, y2) 80 | r3, g3, b3 = cmy_to_rgb(c3, m3, y3) 81 | 82 | color1 = {r1, g1, b1} 83 | color2 = {r2, g2, b2} 84 | color3 = {r3, g3, b3} 85 | 86 | else 87 | color1 = {util.extract_color res["color1"]} 88 | color2 = {util.extract_color res["color2"]} 89 | color3 = {util.extract_color res["color3"]} 90 | 91 | r_all, g_all, b_all = mix_rgb_colors(color1, color2) 92 | n1_r, n1_g, n1_b = mix_rgb_colors({r_all, g_all, b_all}, color3) 93 | 94 | n2_r, n2_g, n2_b = mix_rgb_colors(color1, color2) 95 | n3_r, n3_g, n3_b = mix_rgb_colors(color1, color3) 96 | n4_r, n4_g, n4_b = mix_rgb_colors(color2, color3) 97 | 98 | { 99 | {r: color1[1], g: color1[2], b: color1[3]}, 100 | {r: color2[1], g: color2[2], b: color2[3]}, 101 | {r: color3[1], g: color3[2], b: color3[3]}, 102 | {r: n1_r, g: n1_g, b: n1_b}, 103 | {r: n2_r, g: n2_g, b: n2_b}, 104 | {r: n3_r, g: n3_g, b: n3_b}, 105 | {r: n4_r, g: n4_g, b: n4_b}, 106 | } 107 | 108 | 109 | pathfinding = (shape, xOffset, yOffset) -> 110 | 111 | shape2 = shape\copy! 112 | shape1 = shape2\copy!\sub xOffset, yOffset 113 | shape3 = shape2\copy!\add xOffset, yOffset 114 | 115 | f1 = shape1\copy! 116 | f2 = shape2\copy! 117 | f3 = shape3\copy! 118 | 119 | n1 = shape1\copy! 120 | n2 = shape2\copy! 121 | n3 = shape3\copy! 122 | n4 = shape3\copy! 123 | 124 | AssfPlus._shape.pathfinder "Difference", f1, shape2 125 | AssfPlus._shape.pathfinder "Difference", f1, shape3 126 | 127 | AssfPlus._shape.pathfinder "Difference", f2, shape1 128 | AssfPlus._shape.pathfinder "Difference", f2, shape3 129 | 130 | AssfPlus._shape.pathfinder "Difference", f3, shape1 131 | AssfPlus._shape.pathfinder "Difference", f3, shape2 132 | 133 | AssfPlus._shape.pathfinder "Intersect", n1, n2 134 | AssfPlus._shape.pathfinder "Intersect", n1, n3 135 | 136 | AssfPlus._shape.pathfinder "Difference", n2, shape3 137 | AssfPlus._shape.pathfinder "Difference", n2, f2 138 | 139 | AssfPlus._shape.pathfinder "Difference", n3, shape2 140 | AssfPlus._shape.pathfinder "Difference", n3, f3 141 | 142 | AssfPlus._shape.pathfinder "Difference", n4, shape1 143 | AssfPlus._shape.pathfinder "Difference", n4, f3 144 | 145 | f1, f2, f3, n1, n2, n3, n4 146 | 147 | 148 | main = (sub, sel) -> 149 | 150 | res, btn = createGUI! 151 | 152 | if btn == "Revert" 153 | AssfPlus._util.revertLines sub, sel, "phos.ca" 154 | return 155 | 156 | xOffset = res["xOffset"] 157 | yOffset = res["yOffset"] 158 | 159 | lines = LineCollection sub, sel 160 | return if #lines.lines == 0 161 | 162 | toDelete, toAdd = {}, {} 163 | windowError = AssfPlus._util.windowError 164 | lines\runCallback (lines, line, i) -> 165 | 166 | data = ASS\parse line 167 | table.insert toDelete, line 168 | AssfPlus._util.setOgLineExtradata line, "phos.ca" 169 | 170 | if res["textToShape"] 171 | AssfPlus.lineData.convertTextToShape data 172 | 173 | pos = data\getPosition! 174 | posInLine = data\getTags "position" 175 | if #posInLine == 0 176 | data\insertTags pos 177 | 178 | tags = (data\getEffectiveTags 1, true, true, false).tags 179 | b, g, r = tags.color1\getTagParams! 180 | color = mixColors res, (res["keepBaseColor"] and {r, g, b}) 181 | 182 | textSectionCount = data\getSectionCount ASS.Section.Text 183 | drawingSectionCount = data\getSectionCount ASS.Section.Drawing 184 | 185 | if drawingSectionCount == 0 and textSectionCount == 0 186 | windowError "There is neither text section nor drawing section in the line. Nothing to do here." 187 | 188 | elseif drawingSectionCount > 0 and textSectionCount > 0 189 | windowError "Lines with both text section and drawing section cannot be handled by this script." 190 | 191 | elseif drawingSectionCount > 1 192 | windowError "Lines with multiple drawing section cannot be handled by this script." 193 | 194 | elseif drawingSectionCount > 0 195 | data\callback ((section) -> 196 | f1, f2, f3, n1, n2, n3, n4 = pathfinding section, xOffset, yOffset 197 | 198 | for index, item in ipairs {f1, f2, f3, n1, n2, n3, n4} 199 | continue if item\toString! == "" 200 | section.contours = item.contours 201 | r, g, b = color[index].r, color[index].g, color[index].b 202 | data\replaceTags {ASS\createTag "color1", b, g, r} 203 | table.insert toAdd, ASS\createLine { line } 204 | ), ASS.Section.Drawing 205 | 206 | elseif textSectionCount > 0 207 | 208 | shape = AssfPlus.lineData.getTextShape data 209 | if shape == nil or shape == "" 210 | windowError "Text shape not found." 211 | shape = ASS.Draw.DrawingBase {str: shape} 212 | 213 | f1, f2, f3, n1, n2, n3, n4 = pathfinding shape, xOffset, yOffset 214 | 215 | prepareLine = (shape, color, keepBaseColor = false) -> 216 | return if shape\toString! == "" 217 | data\replaceTags {ASS\createTag "clip_vect", shape} 218 | unless keepBaseColor 219 | r, g, b = color.r, color.g, color.b 220 | data\replaceTags {ASS\createTag "color1", b, g, r} 221 | table.insert toAdd, ASS\createLine { line } 222 | 223 | prepareLine n1, color[4], res["keepBaseColor"] 224 | 225 | data\removeTags "color1", 2, #data.sections 226 | data\cleanTags 1 227 | 228 | prepareLine n2, color[5] 229 | prepareLine n4, color[7] 230 | prepareLine f2, color[2] 231 | 232 | pos\sub xOffset, yOffset 233 | prepareLine f1, color[1] 234 | 235 | pos\add xOffset * 2, yOffset * 2 236 | prepareLine f3, color[3] 237 | prepareLine n3, color[6] 238 | 239 | for ln in *toAdd 240 | lines\addLine ln 241 | 242 | lines\insertLines! 243 | lines\deleteLines toDelete 244 | 245 | 246 | depctrl\registerMacro main 247 | -------------------------------------------------------------------------------- /macros/phos.RotateGradient.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Rotate Gradient" 2 | export script_description = "Create rotated gradient with clip." 3 | export script_author = "PhosCity" 4 | export script_namespace = "phos.RotateGradient" 5 | export script_version = "2.0.2" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depctrl = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 10 | { 11 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 12 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 13 | {"a-mo.Line", version: "1.5.3", url: "https://github.com/TypesettingTools/Aegisub-Motion", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 15 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 16 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 17 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 18 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 19 | } 20 | } 21 | LineCollection, Line, ASS, Functional = depctrl\requireModules! 22 | { :list, :util } = Functional 23 | 24 | tags_grouped = { 25 | {"1c", "3c", "4c"}, 26 | {"alpha", "1a", "2a", "3a", "4a"}, 27 | {"bord", "xbord", "ybord"}, 28 | {"shad", "xshad", "yshad"}, 29 | {"blur", "be"}, 30 | } 31 | tags_flat = list.join unpack tags_grouped 32 | -- Generate a key, value pair of tag's override name and assf tag names 33 | tagMap = {item, (ASS\getTagNames "\\#{item}")[1] for item in *tags_flat} 34 | 35 | --TODO: This was taken and slightly modified from logger module of depctrl. For some reason windowError doesn't work when required from that module. Check why. 36 | windowAssertError = ( condition, errorMessage ) -> 37 | if not condition 38 | aegisub.dialog.display { { class: "label", label: errorMessage } }, { "&Close" }, { cancel: "&Close" } 39 | aegisub.cancel! 40 | else 41 | return condition 42 | 43 | create_dialog = -> 44 | dlg = { 45 | { x: 0, y: 0, width: 2, height: 1, class: "label", label:"Pixels per strip: "}, 46 | { x: 2, y: 0, width: 2, height: 1, class: "intedit", name: "strip", min: 1, value: 1, step: 1 }, 47 | { x: 0, y: 6, width: 2, height: 1, class: "label", label: "Acceleration: " }, 48 | { x: 2, y: 6, width: 2, height: 1, class:"floatedit", name: "accel", value: 1, hint: "1 means no acceleration, >1 starts slow and ends fast, <1 starts fast and ends slow" }, 49 | } 50 | for y, group in ipairs tags_grouped 51 | dlg[#dlg+1] = { name: tag, class: "checkbox", x: x-1, y: y, width: 1, height: 1, label: "\\#{tag}", value: false } for x, tag in ipairs group 52 | 53 | btn, res = aegisub.dialog.display dlg, {"OK", "Cancel"}, {"ok": "OK", "cancel": "Cancel"} 54 | return res if btn else aegisub.cancel! 55 | 56 | 57 | -- Gets clip, tag values, bounding box etc that are required for further processing 58 | prepare_line = (sub, sel, res) -> 59 | lines = LineCollection sub, sel 60 | aegisub.cancel! if #lines.lines < 2 61 | hasClip, clip, tagState, text, bound = false, {}, {} 62 | lines\runCallback ((lines, line, i) -> 63 | aegisub.cancel! if aegisub.progress.is_cancelled! 64 | data = ASS\parse line 65 | -- Collect text of the line 66 | currText = "" 67 | data\callback (section) -> 68 | currText ..= section\getString! if section.class == ASS.Section.Text or section.class == ASS.Section.Drawing 69 | 70 | -- Collect the tag that must be gradiented 71 | effTags = (data\getEffectiveTags -1, true, true, false).tags 72 | for tag in *tags_flat 73 | tagState[i] or= {} 74 | tagState[i][tag] = effTags[tagMap[tag]] if res[tag] 75 | 76 | -- Collect vectorial clip from the line 77 | clipTable = data\getTags "clip_vect" 78 | if #clipTable != 0 and not hasClip 79 | hasClip = true 80 | for index, cnt in ipairs clipTable[1].contours[1].commands -- Is this the best way to loop through co-ordinate? 81 | data\removeTags "clip_vect" -- Clip affects the bounding box of the line. 82 | break if index == 4 83 | x, y = cnt\get! 84 | table.insert clip, x 85 | table.insert clip, y 86 | 87 | -- Collect bounding box of the line 88 | currBound = data\getLineBounds! 89 | 90 | -- Check if text of selected lines differ. They should not. Also update bounding box with the bigger bound. (Tags like blur increase bounding box of lines) 91 | if i == 1 92 | text, bound = currText, currBound 93 | else 94 | windowAssertError text == currText, "You must select the lines that have same text or drawing." 95 | bound = currBound if bound.w < currBound.w or bound.h < currBound.h 96 | ), true 97 | windowAssertError hasClip, "No clip found in the selected lines." 98 | windowAssertError #clip == 6, "The vectorial clip must have at least 3 points." 99 | return clip, tagState, bound 100 | 101 | 102 | -- For 3 points, determine the point of intersection of line passing through first 2 points and a line perpendicular to it passing through 3rd point 103 | intersect_perpendicular = (clip) -> 104 | x1, y1, x2, y2, x3, y3 = unpack clip 105 | k = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / ((x2 - x1)^2 + (y2 - y1)^2) 106 | x = x1 + k * (x2 - x1) 107 | y = y1 + k * (y2 - y1) 108 | return x, y 109 | 110 | 111 | -- Divides a line between 2 points in equal interval (user defined pixels in this case) 112 | divide = (x1, y1, x2, y2, pixel)-> 113 | distance = math.sqrt((x2 - x1)^2 + (y2 - y1)^2) 114 | no_of_section = math.ceil(distance / pixel) 115 | points = {} 116 | for i = 0, no_of_section 117 | k = i / no_of_section 118 | x = x1 + k * (x2 - x1) 119 | y = y1 + k * (y2 - y1) 120 | points[i] or= {} 121 | points[i]["x"] = x 122 | points[i]["y"] = y 123 | return points 124 | 125 | 126 | -- Determine the clip for each interval 127 | -- (x1, y1) ---------------------- (x2,y2) 128 | -- | | 129 | -- (x1, y4) ---------------------- (x2,y3) 130 | clipCreator = (points, bounds, slope, gradientDirection) -> 131 | x1, x2 = bounds[1].x, bounds[2].x 132 | m = ASS.Draw.Move 133 | l = ASS.Draw.Line 134 | clip = {} 135 | for i = 1, #points 136 | prevIntercept = points[i-1]["y"] - slope * points[i-1]["x"] 137 | y1 = slope * x1 + prevIntercept 138 | y2 = slope * x2 + prevIntercept 139 | currIntercept = points[i]["y"] - slope * points[i]["x"] 140 | y4 = slope * x1 + currIntercept 141 | y3 = slope * x2 + currIntercept 142 | 143 | -- Create overlap in the clip 144 | if gradientDirection == "up" 145 | y3 -= 0.75 146 | y4 -= 0.75 147 | elseif gradientDirection == "down" 148 | y1 -= 0.75 149 | y2 -= 0.75 150 | 151 | clip[i] = ASS\createTag "clip_vect", m(x1, y1), l(x2, y2), l(x2, y3), l(x1, y4) 152 | return clip 153 | 154 | 155 | -- If any clip falls outside the text or shape, it'll skew the gradient. So remove them. 156 | removeInvisibleClip = (lines, klip) -> 157 | newClip = {} 158 | for i = 1, #klip 159 | newLine = Line lines[1], lines 160 | data = ASS\parse newLine 161 | data\replaceTags klip[i] 162 | bound = data\getLineBounds! 163 | table.insert newClip, klip[i] if bound.h != 0 164 | return newClip 165 | 166 | 167 | -- Interpolate tags between two lines by a factor 168 | interpolate = (first_line, last_line, tag, factor) -> 169 | local finalValue 170 | switch tag 171 | when "1c", "3c", "4c" 172 | finalValue = first_line[tag]\copy! 173 | b1, g1, r1 = first_line[tag]\getTagParams! 174 | b2, g2, r2 = last_line[tag]\getTagParams! 175 | finalValue.r.value, finalValue.g.value, finalValue.b.value = util.extract_color(util.interpolate_color factor, util.ass_color(r1, g1, b1), util.ass_color(r2, g2, b2)) 176 | else 177 | finalValue = first_line[tag]\lerp last_line[tag], factor 178 | return finalValue 179 | 180 | 181 | main = (sub, sel) -> 182 | res = create_dialog! 183 | clip, tagState, bounds = prepare_line sub, sel, res 184 | x, y = intersect_perpendicular(clip) 185 | x1, y1, x2, y2, x3, y3 = unpack clip 186 | gradientDirection = "down" 187 | gradientDirection = "up" if tonumber(y) > tonumber(y3) 188 | points = divide(x, y, x3, y3 , res.strip) 189 | slope = (y2-y1)/(x2-x1) 190 | klip = clipCreator(points, bounds, slope, gradientDirection) 191 | 192 | lines = LineCollection sub, sel 193 | klip = removeInvisibleClip lines, klip 194 | 195 | -- Stores how many frames between each key line 196 | frames_per, prev_end_frame = {}, 0 197 | avg_frame_cnt = #klip/(#lines.lines-1) 198 | for i = 1, #lines.lines-1 199 | curr_end_frame = math.ceil i*avg_frame_cnt 200 | frames_per[i] = curr_end_frame - prev_end_frame 201 | prev_end_frame = curr_end_frame 202 | 203 | toDelete, count = {}, 1 204 | lines\runCallback ((lines, line, i) -> 205 | aegisub.cancel! if aegisub.progress.is_cancelled! 206 | toDelete[#toDelete+1] = line 207 | if i != 1 208 | first_line = tagState[i-1] 209 | last_line = tagState[i] 210 | -- For the number of lines indicated by the frames_per table, create a gradient 211 | for j = 1, frames_per[i-1] 212 | factor = frames_per[i-1] < 2 and 1 or (j-1)^res.accel/(frames_per[i-1]-1)^res.accel 213 | newLine = Line line, lines 214 | newData = ASS\parse newLine 215 | newData\replaceTags klip[count] 216 | count += 1 217 | for tag in *tags_flat 218 | newData\replaceTags interpolate(first_line, last_line, tag, factor) if res[tag] 219 | newData\commit! 220 | lines\addLine newLine 221 | ), true 222 | lines\insertLines! 223 | lines\deleteLines toDelete 224 | return [sel[1]+i for i = 0, #klip-1] -- Return selection of all newly added lines 225 | 226 | depctrl\registerMacro main 227 | -------------------------------------------------------------------------------- /misc/phos.ink2ass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import inkex 3 | 4 | __version__ = "1.0.7" 5 | 6 | 7 | def round_number(num, decimals=3): 8 | rounded = round(float(num), decimals) 9 | return int(rounded) if rounded.is_integer() else rounded 10 | 11 | 12 | class ShapeProcessor: 13 | def __init__(self, element, options, svg): 14 | self.element = element 15 | self.path = None 16 | self.style = element.specified_style() 17 | self.ass_tags = { 18 | "an": 7, 19 | "bord": 0, 20 | "shad": 0, 21 | "fscx": 100, 22 | "fscy": 100, 23 | "pos": [0, 0], 24 | } 25 | self.options = options 26 | self.svg = svg 27 | # self.notes = [] 28 | 29 | def create_ass_tags(self): 30 | """Create tags based on the attributes of the element.""" 31 | 32 | if opacity := self.get_opacity("opacity"): 33 | self.ass_tags["alpha"] = opacity 34 | 35 | fill_color = self.get_color("fill") 36 | if fill_color: 37 | self.ass_tags["c"] = fill_color 38 | 39 | if fill_opacity := self.get_opacity("fill-opacity"): 40 | self.ass_tags["1a"] = fill_opacity 41 | else: 42 | self.ass_tags["1a"] = "&HFF&" 43 | 44 | stroke_color = self.get_color("stroke") 45 | stroke_width = self.get_stroke_width() 46 | if stroke_width and stroke_color: 47 | self.ass_tags["bord"] = stroke_width 48 | self.ass_tags["3c"] = stroke_color 49 | 50 | if stroke_opacity := self.get_opacity("stroke-opacity"): 51 | self.ass_tags["3a"] = stroke_opacity 52 | 53 | self.ass_tags["p"] = 1 54 | 55 | def get_opacity(self, attrib): 56 | """Extract the opacity attribute from the element and convert to ass hex""" 57 | 58 | opacity = float(self.style.get(attrib, 1.0)) 59 | if opacity != 1.0: 60 | return f"&H{int((1 - opacity) * 255):02X}&" 61 | return False 62 | 63 | def get_color(self, attrib): 64 | """Extract the fill color attribute from the element.""" 65 | 66 | color_attr = self.style(attrib) 67 | 68 | if color_attr is None: 69 | return False 70 | 71 | if isinstance(color_attr, inkex.LinearGradient): 72 | # Get first color of the gradient for now 73 | first_stop = color_attr.href.stops[0] 74 | fisrt_stop_style = first_stop.specified_style() 75 | color_str = fisrt_stop_style.get("stop-color") 76 | 77 | # # TODO: Add support for linear gradients. 78 | # # Retrieve gradient attributes 79 | # x1 = color_attr.get("x1", "0%") 80 | # y1 = color_attr.get("y1", "0%") 81 | # x2 = color_attr.get("x2", "100%") 82 | # y2 = color_attr.get("y2", "0%") 83 | # inkex.errormsg(f"x1: {x1}, y1: {y1}, x2: {x2}, y2: {y2}") 84 | # # Process stops 85 | # for stop in color_attr.href.stops: 86 | # stop_style = stop.specified_style() 87 | # color = stop_style.get("stop-color") 88 | # opacity = stop_style.get("stop-opacity") 89 | # offset = stop.attrib.get("offset") 90 | # inkex.errormsg(f"color: {color}, opacity: {opacity}, offset: {offset}") 91 | 92 | # elif isinstance(color_attr, inkex.RadialGradient): 93 | # inkex.utils.debug("It's radial gradient.") 94 | else: 95 | color_str = color_attr 96 | 97 | color = inkex.Color(color_str) 98 | return f"&H{color.blue:02X}{color.green:02X}{color.red:02X}&" 99 | 100 | def get_stroke_width(self): 101 | """Extract the stroke attribute from the element.""" 102 | 103 | stroke_width = self.style.get("stroke-width") 104 | if not stroke_width: 105 | return False 106 | 107 | paint_order = self.style.get("paint-order", "normal") 108 | if paint_order == "normal": 109 | paint_order = "fill stroke markers" 110 | 111 | paint_order = paint_order.split() 112 | stroke_index = paint_order.index("stroke") 113 | fill_index = paint_order.index("fill") 114 | if self.options.stroke_preservation == "strict" and stroke_index > fill_index: 115 | inkex.errormsg( 116 | f'Error:\n\nThe stroke order of object "{self.element.get_id()}" has stroke above fill which will have wrong output in ASS.\nYou see this message because have chosen strict stroke preservation in the GUI.\n\nFor accurate output, either change stroke order in Inkscape to have fill above stroke or change the stroke preservation in GUI if you don\'t mind applying path effect to your object.' 117 | ) 118 | exit() 119 | elif ( 120 | self.options.stroke_preservation == "use_path_effects" 121 | and stroke_index > fill_index 122 | ): 123 | paint_order[stroke_index] = "fill" 124 | paint_order[fill_index] = "stroke" 125 | 126 | offset_param_dict = { 127 | "update_on_knot_move": "true", 128 | "attempt_force_join": "false", 129 | "miter_limit": "4", 130 | "offset": -float(stroke_width) * 0.5, 131 | "unit": "px", 132 | "linejoin_type": "round", 133 | "lpeversion": "1.3", 134 | "is_visible": "true", 135 | "effect": "offset", 136 | } 137 | effect = inkex.PathEffect() 138 | for key in offset_param_dict: 139 | effect.set(key, offset_param_dict[key]) 140 | self.svg.defs.add(effect) 141 | self.element.set("inkscape:original-d", self.element.attrib["d"]) 142 | self.element.set("inkscape:path-effect", effect.get_id(as_url=1)) 143 | 144 | self.element.style.update( 145 | { 146 | "paint-order": " ".join(paint_order), 147 | "stroke-width": float(stroke_width) * 2, 148 | } 149 | ) 150 | return round_number(float(stroke_width) * 2, 2) 151 | else: 152 | return round_number(float(stroke_width) * 0.5, 2) 153 | 154 | def handle_clip_path(self): 155 | """Process the clip-path attribute of the element if present.""" 156 | 157 | clip_path = self.element.get("clip-path") 158 | if clip_path is None: 159 | return 160 | 161 | clip_path = clip_path[5:-1] # Extract the ID between 'url(#' and ')' 162 | clip_elem = self.svg.getElementById(clip_path) 163 | self.ass_tags["clip"] = f"({self.convert_path(clip_elem.to_path_element())})" 164 | 165 | def convert_path(self, shape_elem=None): 166 | """Convert the path of the shape element to the ass format.""" 167 | 168 | if shape_elem is None: 169 | shape_elem = self.element 170 | # Apply any transformations and viewBox scaling 171 | shape_elem.apply_transform() 172 | 173 | # Convert commands like A, S, Q, and T to cubic bezier 174 | elem = shape_elem.path.to_superpath().to_path() 175 | 176 | # Convert all commands to absolute positions 177 | elem = elem.to_absolute() 178 | 179 | viewBoxScale = self.svg.scale 180 | if viewBoxScale != 1: 181 | elem.scale(viewBoxScale, viewBoxScale, True) 182 | 183 | # After this, path will now contain only M, L, C, and Z commands 184 | path = [] 185 | prev_cmd = None 186 | for idx, segment in enumerate(elem): 187 | cmd = (segment.letter).lower() 188 | if cmd == "z": 189 | continue 190 | cmd = "b" if cmd == "c" else cmd 191 | if cmd != prev_cmd: 192 | path.append(cmd) 193 | prev_cmd = cmd 194 | path.extend([round_number(num) for num in segment.args]) 195 | 196 | return " ".join(map(str, path)) 197 | 198 | def generate_lines(self): 199 | """Combine tags, clips, and path to generate final output lines.""" 200 | 201 | tags = [] 202 | for key, value in self.ass_tags.items(): 203 | if isinstance(value, list): 204 | value_str = f"({value[0]},{value[1]})" 205 | else: 206 | value_str = str(value) 207 | tags.append(f"\\{key}{value_str}") 208 | tag_string = "{" + "".join(tags) + "}" 209 | 210 | line = "" 211 | match self.options.output_format: 212 | case "drawing": 213 | line = tag_string + self.path 214 | case "clip": 215 | line = "\\clip(" + self.path + ")" 216 | case "iclip": 217 | line = "\\iclip(" + self.path + ")" 218 | case "line": 219 | line = ( 220 | "Dialogue: 0,0:00:00.00,0:00:00.02,Default,,0,0,0,," 221 | + tag_string 222 | + self.path 223 | ) 224 | 225 | return line 226 | 227 | def process(self): 228 | """Perform all steps in sequence to process the shape element.""" 229 | 230 | self.create_ass_tags() 231 | self.handle_clip_path() 232 | if self.element.TAG != "path": 233 | self.element = self.element.to_path_element() 234 | self.path = self.convert_path() 235 | return self.generate_lines() 236 | 237 | 238 | class ProcessElements(inkex.EffectExtension): 239 | def add_arguments(self, pars): 240 | pars.add_argument("--output_format", type=str, help="types of output") 241 | pars.add_argument("--stroke_preservation", type=str, help="types of output") 242 | 243 | def process_element(self, element): 244 | """Processes a single SVG element, handling groups recursively.""" 245 | 246 | if isinstance(element, inkex.Group): 247 | element.bake_transforms_recursively() # Apply transformations to the group 248 | for child in element: 249 | self.process_element(child) # Recurse into group elements 250 | elif isinstance(element, inkex.ShapeElement): 251 | if element.TAG in { 252 | "path", 253 | "rect", 254 | "circle", 255 | "ellipse", 256 | "line", 257 | "polyline", 258 | "polygon", 259 | }: 260 | processor = ShapeProcessor(element, self.options, self.svg) 261 | line = processor.process() 262 | print(line) 263 | 264 | def effect(self): 265 | # Loop through all elements in the SVG 266 | unnecessary = ["g", "defs", "svg", "metadata", "namedview"] 267 | for element in self.svg.descendants(): 268 | if element.TAG in unnecessary: 269 | continue 270 | self.process_element(element) 271 | 272 | 273 | if __name__ == "__main__": 274 | ProcessElements().run() 275 | -------------------------------------------------------------------------------- /source/phos.RemoveTags.norg: -------------------------------------------------------------------------------- 1 | @document.meta 2 | title: phos.RemoveTags 3 | description: 4 | authors: phos 5 | categories: 6 | created: 2024-09-16T17:58:55+0530 7 | updated: 2024-11-17T17:03:36+0530 8 | version: 1.1.1 9 | tangle: { 10 | languages: { 11 | moon: ~/Git/Repos/Aegisub-Scripts/macros/phos.RemoveTags.moon 12 | } 13 | scope: all 14 | } 15 | @end 16 | 17 | * Script Details 18 | @code moon 19 | export script_name = "Remove Tags" 20 | export script_description = "Dynamically remove tags based on selection" 21 | export script_version = "1.0.3" 22 | export script_author = "PhosCity" 23 | export script_namespace = "phos.RemoveTags" 24 | 25 | @end 26 | 27 | * Dependency Control 28 | @code moon 29 | DependencyControl = require "l0.DependencyControl" 30 | depctrl = DependencyControl{ 31 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json", 32 | { 33 | {"a-mo.LineCollection", version: "1.3.0", url: "https: //github.com/TypesettingTools/Aegisub-Motion", 34 | feed: "https: //raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 35 | {"l0.ASSFoundation", version: "0.5.0", url: "https: //github.com/TypesettingTools/ASSFoundation", 36 | feed: "https: //raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 37 | {"l0.Functional", version: "0.6.0", url: "https://github.com/TypesettingTools/Functional", 38 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 39 | {"phos.AssfPlus", version: "1.0.4", url: "https://github.com/PhosCity/Aegisub-Scripts", 40 | feed: "https://raw.githubusercontent.com/PhosCity/Aegisub-Scripts/main/DependencyControl.json"}, 41 | } 42 | } 43 | LineCollection, ASS, Functional, AssfPlus = depctrl\requireModules! 44 | {:list} = Functional 45 | 46 | @end 47 | 48 | * Collect Tags 49 | This collects all the tags in the selected lines along with some info about the tags. 50 | @code moon 51 | collectTags = (lines) -> 52 | @end 53 | ** Collect tags using AssfPlus 54 | Most of the actual collection is already handled by AssfPlus so we don't need to do much here. 55 | @code moon 56 | collection = AssfPlus.lineCollection.collectTags lines, true 57 | @end 58 | ** Now we're adding more info to that collection 59 | @code moon 60 | with collection 61 | @end 62 | *** Buttons 63 | We add most of the buttons here. There is another button that is conditional so we add it later. 64 | @code moon 65 | .buttons = {"Remove Tags", "Remove All", "Cancel"} 66 | @end 67 | *** Groups 68 | These are the group names that will allow us to remove a bunch of related tags at once. 69 | @code moon 70 | .removeGroup = {color: false, alpha: false, rotation: false, scale: false, perspective: false, inline_except_last: .multiple_inline_tags} 71 | @end 72 | *** Groups Tags 73 | Here, we add what tags are associated with the group name we just added above. 74 | @code moon 75 | .removeGroupTags = { 76 | color: {"color1", "color2", "color3", "color4"} 77 | alpha: {"alpha", "alpha1", "alpha2", "alpha3", "alpha4"} 78 | rotation: {"angle", "angle_x", "angle_y"} 79 | scale: {"fontsize", "scale_x", "scale_y"} 80 | perspective: {"angle", "angle_x", "angle_y", "shear_x", "shear_y"} 81 | } 82 | @end 83 | *** Hint 84 | Here are the hints that will be used all over the GUI later on 85 | @code moon 86 | .hint = { 87 | color: "c, 1c, 2c, 3c, 4c" 88 | alpha: "alpha, 1a, 2a, 3a, 4a" 89 | rotation: "frz, frx, fry" 90 | scale: "fs, fscx, fscy" 91 | perspective: "frz, frx, fry, fax, fay, org" 92 | inline_except_last: "Remove all inline tags except the last one" 93 | start_tag: "Remove from start tags only" 94 | inline_tags: "Remove from inline tags only" 95 | transform: "Remove from transform only" 96 | invert: "Remove all except selected" 97 | } 98 | @end 99 | *** Invert 100 | Add invert as an option which allows us to invert the selection of tags to be removed meaning tags except the selected will be removed. 101 | @code moon 102 | .tagTypes.invert = true 103 | @end 104 | ** Activate groups in GUI 105 | Only groups that has tags present in the line are shown in the GUI so we activate them here. 106 | @code moon 107 | for tag in *collection.tagList 108 | with collection.removeGroup 109 | if tag\match "color" 110 | .color = true 111 | elseif tag\match "alpha" 112 | .alpha = true 113 | elseif tag\match "angle" 114 | .rotation = true 115 | .perspective = true 116 | elseif tag\match "shear" or tag == "origin" 117 | .perspective = true 118 | elseif tag\match "scale" or tag == "fontsize" 119 | .scale = true 120 | @end 121 | ** Return collection 122 | @code moon 123 | collection 124 | @end 125 | * Create GUI 126 | Dynamically creates a gui depending on the options set in collection table 127 | @code moon 128 | createGui = (collection) -> 129 | @end 130 | ** Left portion of the GUI 131 | @code moon 132 | y, dialog = 0, {} 133 | for key, value in pairs collection.removeGroup 134 | continue unless value 135 | label = ("Remove all #{key}")\gsub "_", " " 136 | dialog[#dialog+1]= {x: 0, y: y, class: "checkbox", label: label, name: "group_"..key, hint: collection.hint[key]} 137 | y += 1 138 | @end 139 | ** Right portion of the GUI 140 | @code moon 141 | startX = 0 142 | if #dialog > 0 143 | table.insert collection.buttons, 1, "Remove Group" 144 | startX = 1 145 | x = startX 146 | @end 147 | *** Top Row 148 | @code moon 149 | for key in *[ item for item in *{"start_tag", "inline_tags", "transforms", "invert"} when collection.tagTypes[item]] -- Manually sorting since lua doesn't loop through table in order 150 | label = key\gsub("^%l", string.upper)\gsub("_%l", string.upper)\gsub("_", " ") 151 | dialog[#dialog+1]= {x: x, y: 0, class: "checkbox", label: label, name: key, hint: collection.hint[key]} 152 | x += 1 153 | @end 154 | *** Determine the number of columns in GUI 155 | Currently variable x holds the number of top row checkboxes. 156 | The number of columns should be at least equal to that much. 157 | However, it can be higher if the square root of the total number of collected tags is greater than x. 158 | We find square root because we want a square-ish grid of tags in GUI. 159 | @code moon 160 | column = math.max math.ceil(math.sqrt #collection.tagList), x 161 | @end 162 | *** Dynamically create GUI 163 | We finally add tags checkboxes to the GUI. 164 | @code moon 165 | count = 0 166 | for y = 1, column 167 | for x = startX, column + startX - 1 168 | count += 1 169 | break if count > #collection.tagList 170 | label = (ASS.toFriendlyName[collection.tagList[count]])\gsub("\\1c%s+", "")\gsub("\\", "") 171 | dialog[#dialog+1] = {x: x, y: y, class: "checkbox", label: label, name: collection.tagList[count]} 172 | @end 173 | ** Create Aegisub dialog 174 | @code moon 175 | btn, res = aegisub.dialog.display dialog, collection.buttons, {"cancel": "Cancel"} 176 | aegisub.cancel! unless btn 177 | @end 178 | ** Return 179 | @code moon 180 | btn, res 181 | @end 182 | 183 | * Main Function 184 | @code moon 185 | main = (sub, sel) -> 186 | @end 187 | ** Line Collection 188 | @code moon 189 | lines = LineCollection sub, sel 190 | return if #lines.lines == 0 191 | @end 192 | ** Collect Tags 193 | @code moon 194 | collection = collectTags lines 195 | @end 196 | ** Create GUI 197 | @code moon 198 | btn, res = createGui collection 199 | @end 200 | ** Loop through line 201 | @code moon 202 | lines\runCallback (lines, line, i) -> 203 | @end 204 | *** Progress Reporting 205 | @code moon 206 | AssfPlus._util.checkCancellation! 207 | AssfPlus._util.progress "Removing tags", i, #lines.lines 208 | @end 209 | *** Parse line 210 | 211 | @code moon 212 | data = ASS\parse line 213 | tagSectionCount = data\getSectionCount ASS.Section.Tag 214 | transforms = data\getTags "transform" 215 | switch btn 216 | @end 217 | **** Remove All 218 | This removes all tags. 219 | If start tag is checked in GUI, all start tags are removed. 220 | If inline tag is checked in GUI, all inline tags are removed. 221 | If none of them is checked, all tags are removed. 222 | @code moon 223 | when "Remove All" 224 | if res.start_tag 225 | data\removeTags _, 1, 1 226 | elseif res.inline_tags 227 | data\removeTags _, 2, tagSectionCount 228 | else 229 | data\stripTags! 230 | @end 231 | **** Remove Group 232 | This removes all tags in the group. 233 | @code moon 234 | when "Remove Group" 235 | @end 236 | ***** Remove all inline tags except the last one 237 | This is mostly useful for undoing gradients. 238 | @code moon 239 | if res["group_inline_except_last"] 240 | data\removeTags _, (collection.tagTypes.start_tag and 2 or 1), tagSectionCount - 1 241 | @end 242 | ***** Remove rest of the groups 243 | Here we remove all other groups. 244 | Clicking on start tag or inline tag will cause the tags associated with a group to be removed from start tag or inline tag respectively. 245 | @code moon 246 | else 247 | start, end_, tagsToDelete = 1, tagSectionCount, {} 248 | end_ = 1 if res.start_tag 249 | start = 2 if res.inline_tags 250 | for key, value in pairs collection.removeGroupTags 251 | continue unless res["group_" .. key] 252 | tagsToDelete = list.join tagsToDelete, value 253 | tagsToDelete = list.diff ASS.tagSortOrder, tagsToDelete if res.invert 254 | data\removeTags tagsToDelete, start, end_ 255 | @end 256 | **** Remove Tags 257 | This removes individual tags selected in the GUI. 258 | @code moon 259 | when "Remove Tags" 260 | @end 261 | ***** Find all the tags that should be removed 262 | We already have tags that user selected in the GUI. 263 | If user also checked Invert checkbox, we then select all the other tags other than what user selected for removal. 264 | @code moon 265 | local tagsToDelete 266 | if res.invert 267 | tagsToDelete = [tag for tag in *collection.tagList when not res[tag]] 268 | else 269 | tagsToDelete = [tag for tag in *collection.tagList when res[tag]] 270 | @end 271 | ***** Start Tags 272 | @code moon 273 | if res.start_tag 274 | data\removeTags tagsToDelete, 1, 1 275 | @end 276 | ***** Inline Tags 277 | @code moon 278 | elseif res.inline_tags 279 | data\removeTags tagsToDelete, 2, tagSectionCount 280 | @end 281 | ***** Transforms 282 | @code moon 283 | elseif res.transforms 284 | for tr in *transforms 285 | tr.tags\removeTags tagsToDelete 286 | @end 287 | ***** Remove all tag instances 288 | @code moon 289 | else 290 | data\removeTags tagsToDelete 291 | @end 292 | *** Cleanup and Commit 293 | @code moon 294 | data\commit! 295 | line.text = line.text\gsub "{}", "" 296 | lines\replaceLines! 297 | @end 298 | * Register the macro to Aegisub 299 | @code moon 300 | depctrl\registerMacro main 301 | @end 302 | --------------------------------------------------------------------------------