├── 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 | 
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 | 
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 | 
33 |
34 | === "Same mask without blur"
35 |
36 | 
37 |
38 | Example 2:
39 |
40 | === "Text gradient"
41 |
42 | 
43 |
44 | === "Same text without blur"
45 |
46 | 
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 | 
6 |
7 | {: 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 | 
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 | 
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 | {: 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | {: 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 | 
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 | 
14 |
15 | 
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 | 
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 | {: 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 | 
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 | 
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 = ""
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 | 
25 |
26 | Fold Operations GUI:
27 |
28 | 
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 | 
42 |
43 | === "Same fold without comments"
44 |
45 | 
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 | 
68 |
69 | If the fold is nested, it will show you by the number of arrows before fold names.
70 |
71 | 
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 | 
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 | 
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 | 
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 | {: style="height:611px;width:796px"}
10 |
11 | # Buttons
12 |
13 | {: 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 | {: 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 |
--------------------------------------------------------------------------------