├── DependencyControl.json ├── LICENSE ├── README.md ├── l0.ASSWipe.moon ├── l0.CascadeTransforms.moon ├── l0.HighlightSubstring.moon ├── l0.InsertLineBreaks.moon ├── l0.LerpByChar.moon ├── l0.MergeDrawings.moon ├── l0.MoveAlongPath.moon ├── l0.Nudge.moon ├── l0.PasteAiLines.moon ├── l0.ShakeIt.moon ├── l0.SplitLines.moon └── l0.VerticalText.moon /DependencyControl.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencyControlFeedFormatVersion": "0.2.0", 3 | "name": "line0's Aegisub Scripts", 4 | "description": "Main repository for all of line0's automation macros.", 5 | "baseUrl": "https://github.com/TypesettingTools/line0-Aegisub-Scripts", 6 | "fileBaseUrl": "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts", 7 | "url": "@{baseUrl}", 8 | "maintainer": "line0", 9 | "macros": { 10 | "l0.ASSWipe": { 11 | "url": "@{baseUrl}#@{namespace}", 12 | "author": "line0", 13 | "name": "ASSWipe", 14 | "description": "Performs script cleanup, removes unnecessary tags and lines.", 15 | "channels": { 16 | "master": { 17 | "version": "0.5.0", 18 | "released": "2019-10-24", 19 | "default": false, 20 | "files": [ 21 | { 22 | "name": ".moon", 23 | "url": "@{fileBaseUrl}@{fileName}", 24 | "sha1": "0A05260E0358EF23897C3CFDCAEFC888AABC6A5B" 25 | }, 26 | { "name": ".lua", "delete": true } 27 | ], 28 | "requiredModules": [ 29 | { 30 | "moduleName": "a-mo.LineCollection", 31 | "name": "Aegisub-Motion (LineCollection)", 32 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 33 | "version": "1.3.0", 34 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 35 | }, 36 | { 37 | "moduleName": "a-mo.ConfigHandler", 38 | "name": "Aegisub-Motion (ConfigHandler)", 39 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 40 | "version": "1.1.4", 41 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 42 | }, 43 | { 44 | "moduleName": "l0.ASSFoundation", 45 | "name": "ASSFoundation", 46 | "url": "https://github.com/TypesettingTools/ASSFoundation", 47 | "version": "0.5.0", 48 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 49 | }, 50 | { 51 | "moduleName": "l0.Functional", 52 | "name": "(Almost) Functional Suite", 53 | "url": "https://github.com/TypesettingTools/ASSFoundation", 54 | "version": "0.5.0", 55 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 56 | }, 57 | { 58 | "moduleName": "SubInspector.Inspector", 59 | "name": "SubInspector", 60 | "url": "https://github.com/TypesettingTools/SubInspector", 61 | "version": "0.7.2", 62 | "feed": "https://raw.githubusercontent.com/TypesettingTools/SubInspector/master/DependencyControl.json" 63 | } 64 | ], 65 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 66 | }, 67 | "release": { 68 | "version": "0.5.0", 69 | "released": "2019-10-24", 70 | "default": true, 71 | "files": [ 72 | { 73 | "name": ".moon", 74 | "url": "@{fileBaseUrl}@{fileName}", 75 | "sha1": "0A05260E0358EF23897C3CFDCAEFC888AABC6A5B" 76 | }, 77 | { "name": ".lua", "delete": true } 78 | ], 79 | "requiredModules": [ 80 | { 81 | "moduleName": "a-mo.LineCollection", 82 | "name": "Aegisub-Motion (LineCollection)", 83 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 84 | "version": "1.3.0", 85 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 86 | }, 87 | { 88 | "moduleName": "a-mo.ConfigHandler", 89 | "name": "Aegisub-Motion (ConfigHandler)", 90 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 91 | "version": "1.1.4", 92 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 93 | }, 94 | { 95 | "moduleName": "l0.ASSFoundation", 96 | "name": "ASSFoundation", 97 | "url": "https://github.com/TypesettingTools/ASSFoundation", 98 | "version": "0.5.0", 99 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 100 | }, 101 | { 102 | "moduleName": "l0.Functional", 103 | "name": "(Almost) Functional Suite", 104 | "url": "https://github.com/TypesettingTools/ASSFoundation", 105 | "version": "0.5.0", 106 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 107 | }, 108 | { 109 | "moduleName": "SubInspector.Inspector", 110 | "name": "SubInspector", 111 | "url": "https://github.com/TypesettingTools/SubInspector", 112 | "version": "0.7.2", 113 | "feed": "https://raw.githubusercontent.com/TypesettingTools/SubInspector/master/DependencyControl.json" 114 | } 115 | ], 116 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 117 | } 118 | }, 119 | "changelog": { 120 | "0.1.0": [ 121 | "Sync with ASSFoundation changes", 122 | "Start versioning with DependencyControl" 123 | ], 124 | "0.1.3": [ 125 | "Enabled auto-update using DependencyControl", 126 | "Changed config file to \\config\\l0.ASSWipe.json (rename ASSWipe.json to restore your existing configuration)", 127 | "DependencyControl compatibility fixes" 128 | ], 129 | "0.2.0": [ 130 | "Scaled clips and drawings are now handled correctly and can be converted to floating-point.", 131 | "Drawings with extraneous ordinates are now detected and ASSWipe can attempt to fix them." 132 | ], 133 | "0.2.1": [ 134 | "Fixed out-of-memory errors on huge scripts.", 135 | "Fixed the progress not being updated when no lines are being cleaned." 136 | ], 137 | "0.2.2": [ 138 | "ASSWipe now skips and warns about lines using undefined styles (instead of erroring out)." 139 | ], 140 | "0.3.0": [ 141 | "Added support for purging invisible contours from drawings and clips.", 142 | "ASSWipe can now only be run when a video is loaded.", 143 | "The progress bar is now always updated for every processed line." 144 | ], 145 | "0.3.1": [ 146 | "Fixed a bug that would cause a rollback on lines that were cleaned up properly." 147 | ], 148 | "0.3.2": [ 149 | "The 'Combine consecutive identical lines' feature no longer ignores layer order changes inbetween candidate fbf lines. The previous behavior caused the layer blending order to break when certain conditions were met. Thanks coldhell for reporting this one." 150 | ], 151 | "0.3.3": [ 152 | "The blending order check introduced for the 'Combine consecutive identical lines' feature in 0.3.2 now considers merge operations that have already taken place in the current line's blending groups, which considerably improves the number of mergeable lines in some cases." 153 | ], 154 | "0.4.0": [ 155 | "The performance and memory usage of the 'clean drawings' features has been greatly improved.", 156 | "Memory footprint has been decreased by removing the undo text from already cleaned lines.", 157 | "A cleaning option was added to filter/strip extradata from processed scripts.", 158 | "A cleaning option was aded to strip comments from processed lines.", 159 | "The user can now cancel ASSWipe at pretty much any point in time when cleaning is in progress.", 160 | "Upgraded ASSFoundation to v0.4.0 which fixes many cleaning issues experienced in the previous version of ASSWipe. See the ASSFoundation changelog for details.", 161 | "Configuration for the 'quirks mode' added in ASSFoundation v0.4.0 is now exposed to the user, letting them tailor the cleaning process to either libass or vsfitlter.", 162 | "After cleaning contours of a drawing, a dedicated hash check is performed to ensure the process didn't visually change the lines. When it detects the change, it automatically reverts to the original contours and resumes the cleaning process. The amount of affected contours is reported in the final report.", 163 | "Added an option to ignore hash changes caused by contour cleaning. This may visually affect drawings, so don't use this unsupervised!", 164 | "Logging/error reporting was improved and logs are no written to a log file." 165 | ], 166 | "0.5.0": [ 167 | "The 'Merge consecutive tag sections' feature now skips sections that contain any tag from a customizable exception list. Defaults to karaoke tags to avoid breaking poorly written karaoke templater scripts." 168 | ] 169 | } 170 | }, 171 | "l0.CascadeTransforms": { 172 | "url": "@{baseUrl}#@{namespace}", 173 | "author": "line0", 174 | "name": "Cascade Transforms", 175 | "description": "Changes transforms in a line to be transformed in a consecutive fashion, with the transform time being split evenly.", 176 | "channels": { 177 | "master": { 178 | "version": "0.1.0", 179 | "default": false, 180 | "released": "2015-04-17", 181 | "files": [ 182 | { 183 | "name": ".moon", 184 | "url": "@{fileBaseUrl}@{fileName}", 185 | "sha1": "71277E59825413746CCB33231A2FBC396995E8AB" 186 | } 187 | ], 188 | "requiredModules": [ 189 | { 190 | "moduleName": "a-mo.LineCollection", 191 | "name": "Aegisub-Motion (LineCollection)", 192 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 193 | "version": "1.3.0", 194 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 195 | }, 196 | { 197 | "moduleName": "l0.ASSFoundation", 198 | "name": "ASSFoundation", 199 | "url": "https://github.com/TypesettingTools/ASSFoundation", 200 | "version": "0.4.0", 201 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 202 | } 203 | ], 204 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 205 | }, 206 | "release": { 207 | "version": "0.1.0", 208 | "default": true, 209 | "released": "2015-04-17", 210 | "files": [ 211 | { 212 | "name": ".moon", 213 | "url": "@{fileBaseUrl}@{fileName}", 214 | "sha1": "71277E59825413746CCB33231A2FBC396995E8AB" 215 | } 216 | ], 217 | "requiredModules": [ 218 | { 219 | "moduleName": "a-mo.LineCollection", 220 | "name": "Aegisub-Motion (LineCollection)", 221 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 222 | "version": "1.3.0", 223 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 224 | }, 225 | { 226 | "moduleName": "l0.ASSFoundation", 227 | "name": "ASSFoundation", 228 | "url": "https://github.com/TypesettingTools/ASSFoundation", 229 | "version": "0.4.0", 230 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 231 | } 232 | ], 233 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 234 | } 235 | }, 236 | "changelog": { "0.1.0": ["initial release"] } 237 | }, 238 | "l0.HighlightSubstring": { 239 | "url": "@{baseUrl}#@{namespace}", 240 | "author": "line0", 241 | "name": "Highlight Substring", 242 | "description": "Highlights a substring at a given index in a line by underlaying a colored rectangle.", 243 | "channels": { 244 | "master": { 245 | "version": "0.1.1", 246 | "default": false, 247 | "released": "2019-02-17", 248 | "files": [ 249 | { 250 | "name": ".moon", 251 | "url": "@{fileBaseUrl}@{fileName}", 252 | "sha1": "3158BBDB18C8B1208049AB166D1A566359B866FE" 253 | } 254 | ], 255 | "requiredModules": [ 256 | { 257 | "moduleName": "a-mo.LineCollection", 258 | "name": "Aegisub-Motion (LineCollection)", 259 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 260 | "version": "1.3.0", 261 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 262 | }, 263 | { 264 | "moduleName": "l0.ASSFoundation", 265 | "name": "ASSFoundation", 266 | "url": "https://github.com/TypesettingTools/ASSFoundation", 267 | "version": "0.4.0", 268 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 269 | } 270 | ], 271 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 272 | }, 273 | "release": { 274 | "version": "0.1.1", 275 | "default": true, 276 | "released": "2019-02-17", 277 | "files": [ 278 | { 279 | "name": ".moon", 280 | "url": "@{fileBaseUrl}@{fileName}", 281 | "sha1": "3158BBDB18C8B1208049AB166D1A566359B866FE" 282 | } 283 | ], 284 | "requiredModules": [ 285 | { 286 | "moduleName": "a-mo.LineCollection", 287 | "name": "Aegisub-Motion (LineCollection)", 288 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 289 | "version": "1.3.0", 290 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 291 | }, 292 | { 293 | "moduleName": "l0.ASSFoundation", 294 | "name": "ASSFoundation", 295 | "url": "https://github.com/TypesettingTools/ASSFoundation", 296 | "version": "0.4.0", 297 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 298 | } 299 | ], 300 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 301 | } 302 | }, 303 | "changelog": { 304 | "0.1.0": ["initial release"], 305 | "0.1.1": [ 306 | "HighlightSubstring now properly works with ASSFoundation v0.4.0+" 307 | ] 308 | } 309 | }, 310 | "l0.InsertLineBreaks": { 311 | "url": "@{baseUrl}#@{namespace}", 312 | "author": "line0", 313 | "name": "Insert Line Breaks", 314 | "description": "Inserts hard line breaks after n characters, but tries to avoid breaking up words.", 315 | "channels": { 316 | "master": { 317 | "version": "0.2.0", 318 | "default": false, 319 | "released": "2019-02-17", 320 | "files": [ 321 | { "name": ".lua", "delete": true }, 322 | { 323 | "name": ".moon", 324 | "url": "@{fileBaseUrl}@{fileName}", 325 | "sha1": "1AC3A3FD9119800D2F660D107CB0D2FB69A9D2B4" 326 | } 327 | ], 328 | "requiredModules": [ 329 | { 330 | "moduleName": "a-mo.LineCollection", 331 | "name": "Aegisub-Motion (LineCollection)", 332 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 333 | "version": "1.3.0", 334 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 335 | }, 336 | { 337 | "moduleName": "l0.ASSFoundation", 338 | "name": "ASSFoundation", 339 | "url": "https://github.com/TypesettingTools/ASSFoundation", 340 | "version": "0.4.0", 341 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 342 | }, 343 | { 344 | "moduleName": "l0.Functional", 345 | "name": "(Almost) Functional Suite", 346 | "url": "https://github.com/TypesettingTools/Functional", 347 | "version": "0.5.0", 348 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 349 | } 350 | ], 351 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 352 | }, 353 | "release": { 354 | "version": "0.2.0", 355 | "default": true, 356 | "released": "2019-02-17", 357 | "files": [ 358 | { "name": ".lua", "delete": true }, 359 | { 360 | "name": ".moon", 361 | "url": "@{fileBaseUrl}@{fileName}", 362 | "sha1": "1AC3A3FD9119800D2F660D107CB0D2FB69A9D2B4" 363 | } 364 | ], 365 | "requiredModules": [ 366 | { 367 | "moduleName": "a-mo.LineCollection", 368 | "name": "Aegisub-Motion (LineCollection)", 369 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 370 | "version": "1.3.0", 371 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 372 | }, 373 | { 374 | "moduleName": "l0.ASSFoundation", 375 | "name": "ASSFoundation", 376 | "url": "https://github.com/TypesettingTools/ASSFoundation", 377 | "version": "0.4.0", 378 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 379 | }, 380 | { 381 | "moduleName": "l0.Functional", 382 | "name": "(Almost) Functional Suite", 383 | "url": "https://github.com/TypesettingTools/Functional", 384 | "version": "0.5.0", 385 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 386 | } 387 | ], 388 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 389 | } 390 | }, 391 | "changelog": { 392 | "0.0.1": [ 393 | "Sync with ASSFoundation changes", 394 | "Start versioning with DependencyControl" 395 | ], 396 | "0.1.1": [ 397 | "Enabled auto-update using DependencyControl", 398 | "DependencyControl compatibility fixes" 399 | ], 400 | "0.2.0": [ 401 | "The script was ported to MoonScript and dependencies have been updated to their latest versions." 402 | ] 403 | } 404 | }, 405 | "l0.LerpByChar": { 406 | "url": "@{baseUrl}#@{namespace}", 407 | "author": "line0", 408 | "name": "Lerp By Character", 409 | "description": "Linearly interpolates a specified override tag character-by-character between stops in a line.", 410 | "channels": { 411 | "master": { 412 | "version": "0.1.0", 413 | "default": false, 414 | "released": "2019-09-14", 415 | "files": [ 416 | { 417 | "name": ".moon", 418 | "url": "@{fileBaseUrl}@{fileName}", 419 | "sha1": "F26D2838D72F6A61915840A00E1A49D61FA39932" 420 | } 421 | ], 422 | "requiredModules": [ 423 | { 424 | "moduleName": "a-mo.LineCollection", 425 | "name": "Aegisub-Motion (LineCollection)", 426 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 427 | "version": "1.3.0", 428 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 429 | }, 430 | { 431 | "moduleName": "l0.ASSFoundation", 432 | "name": "ASSFoundation", 433 | "url": "https://github.com/TypesettingTools/ASSFoundation", 434 | "version": "0.4.4", 435 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 436 | }, 437 | { 438 | "moduleName": "l0.Functional", 439 | "name": "(Almost) Functional Suite", 440 | "url": "https://github.com/TypesettingTools/Functional", 441 | "version": "0.6.0", 442 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 443 | }, 444 | { 445 | "moduleName": "a-mo.ConfigHandler", 446 | "name": "Aegisub-Motion (ConfigHandler)", 447 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 448 | "version": "1.1.4", 449 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 450 | } 451 | ], 452 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 453 | }, 454 | "release": { 455 | "version": "0.1.0", 456 | "default": true, 457 | "released": "2019-09-14", 458 | "files": [ 459 | { 460 | "name": ".moon", 461 | "url": "@{fileBaseUrl}@{fileName}", 462 | "sha1": "F26D2838D72F6A61915840A00E1A49D61FA39932" 463 | } 464 | ], 465 | "requiredModules": [ 466 | { 467 | "moduleName": "a-mo.LineCollection", 468 | "name": "Aegisub-Motion (LineCollection)", 469 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 470 | "version": "1.3.0", 471 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 472 | }, 473 | { 474 | "moduleName": "l0.ASSFoundation", 475 | "name": "ASSFoundation", 476 | "url": "https://github.com/TypesettingTools/ASSFoundation", 477 | "version": "0.4.4", 478 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 479 | }, 480 | { 481 | "moduleName": "l0.Functional", 482 | "name": "(Almost) Functional Suite", 483 | "url": "https://github.com/TypesettingTools/Functional", 484 | "version": "0.6.0", 485 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 486 | }, 487 | { 488 | "moduleName": "a-mo.ConfigHandler", 489 | "name": "Aegisub-Motion (ConfigHandler)", 490 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 491 | "version": "1.1.4", 492 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 493 | } 494 | ], 495 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 496 | } 497 | }, 498 | "changelog": { 499 | "0.1.0": [ 500 | "Initial release" 501 | ] 502 | } 503 | }, 504 | "l0.MergeDrawings": { 505 | "url": "@{baseUrl}#@{namespace}", 506 | "author": "line0", 507 | "name": "Merge Drawings", 508 | "description": "Moves all drawings found in all selected lines into the first line. Maintains positioning and converts scale as well as alignment.", 509 | "channels": { 510 | "master": { 511 | "version": "0.2.0", 512 | "default": false, 513 | "released": "2019-02-17", 514 | "files": [ 515 | { 516 | "name": ".moon", 517 | "url": "@{fileBaseUrl}@{fileName}", 518 | "sha1": "A85D3A6FF3CDE816BC211625D215696E14A5F893" 519 | } 520 | ], 521 | "requiredModules": [ 522 | { 523 | "moduleName": "a-mo.LineCollection", 524 | "name": "Aegisub-Motion (LineCollection)", 525 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 526 | "version": "1.3.0", 527 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 528 | }, 529 | { 530 | "moduleName": "l0.ASSFoundation", 531 | "name": "ASSFoundation", 532 | "url": "https://github.com/TypesettingTools/ASSFoundation", 533 | "version": "0.4.0", 534 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 535 | } 536 | ], 537 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 538 | }, 539 | "release": { 540 | "version": "0.2.0", 541 | "default": true, 542 | "released": "2019-02-17", 543 | "files": [ 544 | { 545 | "name": ".moon", 546 | "url": "@{fileBaseUrl}@{fileName}", 547 | "sha1": "A85D3A6FF3CDE816BC211625D215696E14A5F893" 548 | } 549 | ], 550 | "requiredModules": [ 551 | { 552 | "moduleName": "a-mo.LineCollection", 553 | "name": "Aegisub-Motion (LineCollection)", 554 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 555 | "version": "1.3.0", 556 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 557 | }, 558 | { 559 | "moduleName": "l0.ASSFoundation", 560 | "name": "ASSFoundation", 561 | "url": "https://github.com/TypesettingTools/ASSFoundation", 562 | "version": "0.4.0", 563 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 564 | } 565 | ], 566 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 567 | } 568 | }, 569 | "changelog": { 570 | "0.1.0": ["initial release"], 571 | "0.2.0": [ 572 | "The final merged drawing is trimmed in such a way that its top-left-most point is also its origin. Position/Move tags are added/adjusted in order for the drawing to retain its position on the canvas.", 573 | "Dependencies have been updated to their latest versions." 574 | ] 575 | } 576 | }, 577 | "l0.MoveAlongPath": { 578 | "url": "@{baseUrl}#@{namespace}", 579 | "author": "line0", 580 | "name": "Move Along Path", 581 | "description": "Moves text along a path specified in a \\clip. Currently only works on fbf lines.", 582 | "channels": { 583 | "master": { 584 | "version": "0.2.1", 585 | "default": false, 586 | "released": "2019-03-28", 587 | "files": [ 588 | { "name": ".lua", "delete": true }, 589 | { 590 | "name": ".moon", 591 | "url": "@{fileBaseUrl}@{fileName}", 592 | "sha1": "C91DD0D7E7C84E45D7F9D413317DE36B56E6C23B" 593 | } 594 | ], 595 | "requiredModules": [ 596 | { "moduleName": "Yutils" }, 597 | { 598 | "moduleName": "a-mo.LineCollection", 599 | "name": "Aegisub-Motion (LineCollection)", 600 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 601 | "version": "1.3.0", 602 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 603 | }, 604 | { 605 | "moduleName": "a-mo.Line", 606 | "name": "Aegisub-Motion (Line)", 607 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 608 | "version": "1.5.3", 609 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 610 | }, 611 | { 612 | "moduleName": "l0.ASSFoundation", 613 | "name": "ASSFoundation", 614 | "url": "https://github.com/TypesettingTools/ASSFoundation", 615 | "version": "0.4.0", 616 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 617 | }, 618 | { 619 | "moduleName": "l0.Functional", 620 | "name": "(Almost) Functional Suite", 621 | "url": "https://github.com/TypesettingTools/Functional", 622 | "version": "0.5.0", 623 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 624 | } 625 | ], 626 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 627 | }, 628 | "release": { 629 | "version": "0.2.1", 630 | "default": true, 631 | "released": "2019-03-28", 632 | "files": [ 633 | { "name": ".lua", "delete": true }, 634 | { 635 | "name": ".moon", 636 | "url": "@{fileBaseUrl}@{fileName}", 637 | "sha1": "C91DD0D7E7C84E45D7F9D413317DE36B56E6C23B" 638 | } 639 | ], 640 | "requiredModules": [ 641 | { "moduleName": "Yutils" }, 642 | { 643 | "moduleName": "a-mo.LineCollection", 644 | "name": "Aegisub-Motion (LineCollection)", 645 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 646 | "version": "1.3.0", 647 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 648 | }, 649 | { 650 | "moduleName": "a-mo.Line", 651 | "name": "Aegisub-Motion (Line)", 652 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 653 | "version": "1.5.3", 654 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 655 | }, 656 | { 657 | "moduleName": "l0.ASSFoundation", 658 | "name": "ASSFoundation", 659 | "url": "https://github.com/TypesettingTools/ASSFoundation", 660 | "version": "0.4.0", 661 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 662 | }, 663 | { 664 | "moduleName": "l0.Functional", 665 | "name": "(Almost) Functional Suite", 666 | "url": "https://github.com/TypesettingTools/Functional", 667 | "version": "0.5.0", 668 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 669 | } 670 | ], 671 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 672 | } 673 | }, 674 | "changelog": { 675 | "0.1.0": [ 676 | "Sync with ASSFoundation changes", 677 | "Start versioning with DependencyControl" 678 | ], 679 | "0.1.2": [ 680 | "Enabled auto-update using DependencyControl", 681 | "DependencyControl compatibility fixes" 682 | ], 683 | "0.1.3": [ 684 | "Generated lines now have the correct line order (within the script and in relation to each other)" 685 | ], 686 | "0.1.4": [ 687 | "The required Yutils module is now loaded correctly on case-sensitive platforms/file systems such as Linux." 688 | ], 689 | "0.2.0": [ 690 | "The script now uses the DependencyControl logger infrastructure and writes a log file to disk.", 691 | "Dependencies have been updated to their latest versions." 692 | ], 693 | "0.2.1": [ 694 | "Entering the automation menu with no lines selected no longer results in an error message. " 695 | ] 696 | } 697 | }, 698 | "l0.Nudge": { 699 | "url": "@{baseUrl}#@{namespace}", 700 | "author": "line0", 701 | "name": "Nudge", 702 | "description": "Provides configurable and hotkeyable tag/line modification macros.", 703 | "channels": { 704 | "master": { 705 | "version": "0.5.0", 706 | "default": false, 707 | "released": "2019-02-17", 708 | "files": [ 709 | { "name": ".lua", "delete": true }, 710 | { 711 | "name": ".moon", 712 | "url": "@{fileBaseUrl}@{fileName}", 713 | "sha1": "E65255219E0C9A29C6DEC27F385DE65F3D2977F7" 714 | } 715 | ], 716 | "requiredModules": [ 717 | { "moduleName": "aegisub.clipboard" }, 718 | { "moduleName": "json" }, 719 | { 720 | "moduleName": "a-mo.LineCollection", 721 | "name": "Aegisub-Motion (LineCollection)", 722 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 723 | "version": "1.3.0", 724 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 725 | }, 726 | { 727 | "moduleName": "l0.ASSFoundation", 728 | "name": "ASSFoundation", 729 | "url": "https://github.com/TypesettingTools/ASSFoundation", 730 | "version": "0.4.0", 731 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 732 | }, 733 | { 734 | "moduleName": "l0.Functional", 735 | "name": "(Almost) Functional Suite", 736 | "url": "https://github.com/TypesettingTools/Functional", 737 | "version": "0.5.0", 738 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 739 | } 740 | ], 741 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 742 | }, 743 | "release": { 744 | "version": "0.5.0", 745 | "default": true, 746 | "released": "2019-02-17", 747 | "files": [ 748 | { "name": ".lua", "delete": true }, 749 | { 750 | "name": ".moon", 751 | "url": "@{fileBaseUrl}@{fileName}", 752 | "sha1": "E65255219E0C9A29C6DEC27F385DE65F3D2977F7" 753 | } 754 | ], 755 | "requiredModules": [ 756 | { "moduleName": "aegisub.clipboard" }, 757 | { "moduleName": "json" }, 758 | { 759 | "moduleName": "a-mo.LineCollection", 760 | "name": "Aegisub-Motion (LineCollection)", 761 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 762 | "version": "1.3.0", 763 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 764 | }, 765 | { 766 | "moduleName": "l0.ASSFoundation", 767 | "name": "ASSFoundation", 768 | "url": "https://github.com/TypesettingTools/ASSFoundation", 769 | "version": "0.4.0", 770 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 771 | }, 772 | { 773 | "moduleName": "l0.Functional", 774 | "name": "(Almost) Functional Suite", 775 | "url": "https://github.com/TypesettingTools/Functional", 776 | "version": "0.5.0", 777 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 778 | } 779 | ], 780 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 781 | } 782 | }, 783 | "changelog": { 784 | "0.3.0": [ 785 | "Sync with ASSFoundation changes", 786 | "Start versioning with DependencyControl" 787 | ], 788 | "0.3.2": [ 789 | "Enabled auto-update using DependencyControl", 790 | "Changed config file to \\config\\l0.Nudge.json (rename nudge.json to restore your existing configuration)", 791 | "DependencyControl compatibility fixes" 792 | ], 793 | "0.3.3": ["ASSFoundation (Common) compatibility fixes"], 794 | "0.3.4": ["Drawing -> Clip conversion is now available."], 795 | "0.4.0": [ 796 | "An option to maintain the appearance of the line when changing the aligment was added to the Align Up/Down/Left/Right operations." 797 | ], 798 | "0.5.0": [ 799 | "The script was ported to MoonScript and dependencies have been updated to their latest versions.", 800 | "The default align nudgers are now configured to maintain line positioning.", 801 | "An option was added for a nudger to strip or keep sections left empty after performing the desired operation." 802 | ] 803 | } 804 | }, 805 | "l0.PasteAiLines": { 806 | "url": "@{baseUrl}#@{namespace}", 807 | "author": "line0", 808 | "name": "Paste AI Lines", 809 | "description": "Convenience macro for pasting full lines exported by AI2ASS.", 810 | "channels": { 811 | "master": { 812 | "version": "0.2.0", 813 | "default": false, 814 | "released": "2019-02-17", 815 | "files": [ 816 | { "name": ".lua", "delete": true }, 817 | { 818 | "name": ".moon", 819 | "url": "@{fileBaseUrl}@{fileName}", 820 | "sha1": "CE50E1E290EB75D7B25419D4333A758405641C7E" 821 | } 822 | ], 823 | "requiredModules": [ 824 | { "moduleName": "aegisub.util" }, 825 | { "moduleName": "aegisub.clipboard" }, 826 | { 827 | "moduleName": "a-mo.LineCollection", 828 | "name": "Aegisub-Motion (LineCollection)", 829 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 830 | "version": "1.3.0", 831 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 832 | }, 833 | { 834 | "moduleName": "a-mo.ConfigHandler", 835 | "name": "Aegisub-Motion (ConfigHandler)", 836 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 837 | "version": "1.1.4", 838 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 839 | }, 840 | { 841 | "moduleName": "l0.ASSFoundation", 842 | "name": "ASSFoundation", 843 | "url": "https://github.com/TypesettingTools/ASSFoundation", 844 | "version": "0.4.0", 845 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 846 | }, 847 | { 848 | "moduleName": "l0.Functional", 849 | "name": "(Almost) Functional Suite", 850 | "url": "https://github.com/TypesettingTools/Functional", 851 | "version": "0.5.0", 852 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 853 | } 854 | ], 855 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 856 | }, 857 | "release": { 858 | "version": "0.2.0", 859 | "default": true, 860 | "released": "2019-02-17", 861 | "files": [ 862 | { "name": ".lua", "delete": true }, 863 | { 864 | "name": ".moon", 865 | "url": "@{fileBaseUrl}@{fileName}", 866 | "sha1": "CE50E1E290EB75D7B25419D4333A758405641C7E" 867 | } 868 | ], 869 | "requiredModules": [ 870 | { "moduleName": "aegisub.util" }, 871 | { "moduleName": "aegisub.clipboard" }, 872 | { 873 | "moduleName": "a-mo.LineCollection", 874 | "name": "Aegisub-Motion (LineCollection)", 875 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 876 | "version": "1.3.0", 877 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 878 | }, 879 | { 880 | "moduleName": "a-mo.ConfigHandler", 881 | "name": "Aegisub-Motion (ConfigHandler)", 882 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 883 | "version": "1.1.4", 884 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 885 | }, 886 | { 887 | "moduleName": "l0.ASSFoundation", 888 | "name": "ASSFoundation", 889 | "url": "https://github.com/TypesettingTools/ASSFoundation", 890 | "version": "0.4.0", 891 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 892 | }, 893 | { 894 | "moduleName": "l0.Functional", 895 | "name": "(Almost) Functional Suite", 896 | "url": "https://github.com/TypesettingTools/Functional", 897 | "version": "0.5.0", 898 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 899 | } 900 | ], 901 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 902 | } 903 | }, 904 | "changelog": { 905 | "0.1.0": [ 906 | "Sync with ASSFoundation changes", 907 | "Start versioning with DependencyControl" 908 | ], 909 | "0.1.2": [ 910 | "Enabled auto-update using DependencyControl", 911 | "Changed config file to \\config\\l0.PasteAILines.json (rename PasteAILines.json to restore your existing configuration)", 912 | "DependencyControl compatibility fixes" 913 | ], 914 | "0.2.0": [ 915 | "The script was ported to MoonScript and dependencies have been updated to their latest versions." 916 | ] 917 | } 918 | }, 919 | "l0.ShakeIt": { 920 | "url": "@{baseUrl}#@{namespace}", 921 | "author": "line0", 922 | "name": "Shake It", 923 | "description": "Lets you add a shaking effect to fbf typesets with configurable constraints.", 924 | "channels": { 925 | "master": { 926 | "version": "0.2.0", 927 | "default": false, 928 | "released": "2019-08-12", 929 | "files": [ 930 | { 931 | "name": ".moon", 932 | "url": "@{fileBaseUrl}@{fileName}", 933 | "sha1": "8951E21B9B58011BA3BB0BA5BA0F60796808CBD4" 934 | } 935 | ], 936 | "requiredModules": [ 937 | { 938 | "moduleName": "a-mo.LineCollection", 939 | "name": "Aegisub-Motion (LineCollection)", 940 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 941 | "version": "1.3.0", 942 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 943 | }, 944 | { 945 | "moduleName": "l0.ASSFoundation", 946 | "name": "ASSFoundation", 947 | "url": "https://github.com/TypesettingTools/ASSFoundation", 948 | "version": "0.4.3", 949 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 950 | }, 951 | { 952 | "moduleName": "l0.Functional", 953 | "name": "(Almost) Functional Suite", 954 | "url": "https://github.com/TypesettingTools/Functional", 955 | "version": "0.5.0", 956 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 957 | }, 958 | { 959 | "moduleName": "a-mo.ConfigHandler", 960 | "name": "Aegisub-Motion (ConfigHandler)", 961 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 962 | "version": "1.1.4", 963 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 964 | } 965 | ], 966 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 967 | }, 968 | "release": { 969 | "version": "0.2.0", 970 | "default": true, 971 | "released": "2019-08-12", 972 | "files": [ 973 | { 974 | "name": ".moon", 975 | "url": "@{fileBaseUrl}@{fileName}", 976 | "sha1": "8951E21B9B58011BA3BB0BA5BA0F60796808CBD4" 977 | } 978 | ], 979 | "requiredModules": [ 980 | { 981 | "moduleName": "a-mo.LineCollection", 982 | "name": "Aegisub-Motion (LineCollection)", 983 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 984 | "version": "1.3.0", 985 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 986 | }, 987 | { 988 | "moduleName": "l0.ASSFoundation", 989 | "name": "ASSFoundation", 990 | "url": "https://github.com/TypesettingTools/ASSFoundation", 991 | "version": "0.4.3", 992 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 993 | }, 994 | { 995 | "moduleName": "l0.Functional", 996 | "name": "(Almost) Functional Suite", 997 | "url": "https://github.com/TypesettingTools/Functional", 998 | "version": "0.5.0", 999 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json" 1000 | }, 1001 | { 1002 | "moduleName": "a-mo.ConfigHandler", 1003 | "name": "Aegisub-Motion (ConfigHandler)", 1004 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1005 | "version": "1.1.4", 1006 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1007 | } 1008 | ], 1009 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 1010 | } 1011 | }, 1012 | "changelog": { 1013 | "0.1.0": [ 1014 | "Ported from old Lua version to use DependencyControl, ASSFoundation, Aegisub-Motion and Functional." 1015 | ], 1016 | "0.1.1": ["Cosmetic improvements to the source code"], 1017 | "0.2.0": [ 1018 | "Added a separate macro for shaking scalar-type tags.", 1019 | "Line grouping can now be guided by a configurable line field or be turned off altogether.", 1020 | "The last dialog choice are now automatically saved and recalled.", 1021 | "Random offset patterns can now be configured to repeat every n line groups." 1022 | ] 1023 | } 1024 | }, 1025 | "l0.SplitLines": { 1026 | "url": "@{baseUrl}#@{namespace}", 1027 | "author": "line0", 1028 | "name": "Split Lines", 1029 | "description": "Splits lines at configurable intervals, specified indexes or at tag boundaries while maintaining appearance.", 1030 | "channels": { 1031 | "master": { 1032 | "version": "0.2.0", 1033 | "default": false, 1034 | "released": "2019-02-17", 1035 | "files": [ 1036 | { 1037 | "name": ".moon", 1038 | "url": "@{fileBaseUrl}@{fileName}", 1039 | "sha1": "941CDCF5EEDD78EA9BED747E8790714EA58A0C1D" 1040 | } 1041 | ], 1042 | "requiredModules": [ 1043 | { 1044 | "moduleName": "a-mo.LineCollection", 1045 | "name": "Aegisub-Motion (LineCollection)", 1046 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1047 | "version": "1.3.0", 1048 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1049 | }, 1050 | { 1051 | "moduleName": "a-mo.ConfigHandler", 1052 | "name": "Aegisub-Motion (ConfigHandler)", 1053 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1054 | "version": "1.1.4", 1055 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1056 | }, 1057 | { 1058 | "moduleName": "l0.ASSFoundation", 1059 | "name": "ASSFoundation", 1060 | "url": "https://github.com/TypesettingTools/ASSFoundation", 1061 | "version": "0.4.0", 1062 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 1063 | }, 1064 | { "moduleName": "aegisub.re" } 1065 | ], 1066 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 1067 | }, 1068 | "release": { 1069 | "version": "0.2.0", 1070 | "default": true, 1071 | "released": "2019-02-17", 1072 | "files": [ 1073 | { 1074 | "name": ".moon", 1075 | "url": "@{fileBaseUrl}@{fileName}", 1076 | "sha1": "941CDCF5EEDD78EA9BED747E8790714EA58A0C1D" 1077 | } 1078 | ], 1079 | "requiredModules": [ 1080 | { 1081 | "moduleName": "a-mo.LineCollection", 1082 | "name": "Aegisub-Motion (LineCollection)", 1083 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1084 | "version": "1.3.0", 1085 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1086 | }, 1087 | { 1088 | "moduleName": "a-mo.ConfigHandler", 1089 | "name": "Aegisub-Motion (ConfigHandler)", 1090 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1091 | "version": "1.1.4", 1092 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1093 | }, 1094 | { 1095 | "moduleName": "l0.ASSFoundation", 1096 | "name": "ASSFoundation", 1097 | "url": "https://github.com/TypesettingTools/ASSFoundation", 1098 | "version": "0.4.0", 1099 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 1100 | }, 1101 | { "moduleName": "aegisub.re" } 1102 | ], 1103 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 1104 | } 1105 | }, 1106 | "changelog": { 1107 | "0.1.0": ["initial commit"], 1108 | "0.2.0": [ 1109 | "Split lines that would only contain whitespace are no longer written to the script.", 1110 | "Dependencies have been updated to their latest versions." 1111 | ] 1112 | } 1113 | }, 1114 | "l0.VerticalText": { 1115 | "url": "@{baseUrl}#@{namespace}", 1116 | "author": "line0", 1117 | "name": "Vertical Text", 1118 | "description": "Splits a line into vertical text.", 1119 | "channels": { 1120 | "master": { 1121 | "version": "0.2.0", 1122 | "default": false, 1123 | "released": "2019-02-17", 1124 | "files": [ 1125 | { "name": ".lua", "delete": true }, 1126 | { 1127 | "name": ".moon", 1128 | "url": "@{fileBaseUrl}@{fileName}", 1129 | "sha1": "B89FEBE845444BF8858E27A09CAA2D237AAA3367" 1130 | } 1131 | ], 1132 | "requiredModules": [ 1133 | { "moduleName": "Yutils" }, 1134 | { 1135 | "moduleName": "a-mo.LineCollection", 1136 | "name": "Aegisub-Motion (LineCollection)", 1137 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1138 | "version": "1.3.0", 1139 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1140 | }, 1141 | { 1142 | "moduleName": "l0.ASSFoundation", 1143 | "name": "ASSFoundation", 1144 | "url": "https://github.com/TypesettingTools/ASSFoundation", 1145 | "version": "0.4.0", 1146 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 1147 | } 1148 | ], 1149 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 1150 | }, 1151 | "release": { 1152 | "version": "0.2.0", 1153 | "default": true, 1154 | "released": "2019-02-17", 1155 | "files": [ 1156 | { "name": ".lua", "delete": true }, 1157 | { 1158 | "name": ".moon", 1159 | "url": "@{fileBaseUrl}@{fileName}", 1160 | "sha1": "B89FEBE845444BF8858E27A09CAA2D237AAA3367" 1161 | } 1162 | ], 1163 | "requiredModules": [ 1164 | { "moduleName": "Yutils" }, 1165 | { 1166 | "moduleName": "a-mo.LineCollection", 1167 | "name": "Aegisub-Motion (LineCollection)", 1168 | "url": "https://github.com/TypesettingTools/Aegisub-Motion", 1169 | "version": "1.3.0", 1170 | "feed": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json" 1171 | }, 1172 | { 1173 | "moduleName": "l0.ASSFoundation", 1174 | "name": "ASSFoundation", 1175 | "url": "https://github.com/TypesettingTools/ASSFoundation", 1176 | "version": "0.4.0", 1177 | "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" 1178 | } 1179 | ], 1180 | "fileBaseUrl": "@{fileBaseUrl}/@{namespace}-v@{version}/@{namespace}" 1181 | } 1182 | }, 1183 | "changelog": { 1184 | "0.0.1": ["initial commit"], 1185 | "0.1.1": [ 1186 | "Enabled auto-update using DependencyControl", 1187 | "DependencyControl compatibility fixes" 1188 | ], 1189 | "0.1.2": [ 1190 | "Sync with ASSFoundation API changes", 1191 | "The required Yutils module is now loaded correctly on case-sensitive platforms/file systems such as Linux." 1192 | ], 1193 | "0.2.0": [ 1194 | "The script was ported to MoonScript and dependencies have been updated to their latest versions." 1195 | ] 1196 | } 1197 | } 1198 | } 1199 | } 1200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 line0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | line0's Aegisub Scripts 2 | ======================= 3 | 4 | 1. [Nudge](#nudge) 5 | 2. [Shake It](#shake-it) 6 | 7 | ---------------------------------- 8 | 9 | 10 | Nudge 11 | ========================== 12 | 13 | Nudge is an automation script for Aegisub that lets you **create your own hotkeyable macros for common tag modifications** like nudging a line up and down, increasing the brightness of signs or cycling through a predefined set of blur states. 14 | 15 | Requirements 16 | ------------ 17 | - Aegisub 3.2.0+ 18 | - [Aegisub-Motion](https://github.com/torque/Aegisub-Motion) 1.0.0+ 19 | - [LuaJSON](https://github.com/harningt/luajson) (shipped with Aegisub-Motion) 20 | - [Yutils](https://github.com/Youka/Yutils) 21 | - Includes from this repo: LineExtend, ASSTags, Common 22 | 23 | Release Packages ship everything but Aegisub-Motion. 24 | 25 | Installation 26 | ------------ 27 | 28 | **From release package:** 29 | 1. Install Aegisub-Motion 30 | 2. Unpack the Nudge archive into your Aegisub automation directory 31 | 3. In Aegisub, rescan your automation folder or restart Aegisub 32 | 33 | **From source repo:** 34 | 1. Install Aegisub-Motion 35 | 2. Clone the repository and copy the *autoload* and *include* folders into your Aegisub automation directory. You only need to take the *Nudge.lua* from the *autoload* folder, but all files from the *include* folder are required. 36 | 37 | 38 | Usage 39 | ---- 40 | When you first load the Nudge script, it will create its own submenu in your automation menu. It also ships with a bunch of default macros to get you started. 41 | 42 | To add, modify or remove Macros, run the *Configure Nudge* macro. Nudge will present you a list of all the existing macros, which you can then customize by adjusting their options: 43 | 44 | - **Macro Name:** Name of Macro. Because hotkeys are registered by command name, you need to update the commands of already hotkeyed macro after changing its name or the hotkey will stop working 45 | - **Target:** The override tag(s) or fields of the line modified by the macro. The list contains all supported override tags as well as some "compound" tags (Colors, Shadows, ...) that will target multiple override tags at once (e.g. *Primary Color* modifies both \c and \1c) 46 | - **Action:** The operation that will be performed on matched override tags (first parameter) and user-supplied values (second parameter). Not all actions support every available tag (refer to the list below). 47 | - **Values:** Second parameter to the operation specified in the *Action* field. Values are separated by commas and usually match the position of the parameters to the specified override tags: *\blur#* takes only 1 parameter, while *\fad(#,#)* takes 2 parameters in tag order. **Exception: ** color tags take 3 base-10 parameters for *r,g,b* (e.g. *255,128,0* instead of *0080FF*). Some action take special set of parameters or ignore user-supplied values altogether (refer to the list below) 48 | - **No Default**: If this checkbox is checked, Nudge will only modify tags already present in the line instead of automatically adding missing tags in case none of the specified tags were found. 49 | - **Remove**: Check this checkbox if you want to remove a Macro and hit *Save*. 50 | 51 | Use the *Add Macro* button to add a new Macro and the *Save* button to save the configuration to the disk. Since Aegisub only allows Macros to be registered when scripts are loaded, **you must reload your scripts** (in the automation menu, *click on Automation...* while holding down *Ctrl*) after adding macros, removing macros and changing macro names for the Automation menu to reflect the changes you just made. 52 | 53 | **Working with Macros:** Run the macro by hotkey or from the menu to make it process all selected lines according to its configuration. 54 | 55 | At this time the script processes all matching tags in a line and inserts style-based defaults for missing tags into the first tag block (Options to customize this behavior are planned). If no tag block is found at the beginning of the line, Nudge will create one unless the *No Default* checkbox is checked. 56 | 57 | Nudge will never output invalid tags (e.g. *\an10*, *\k15.5*) even if the user supplied values are of a bad type or the operation causes the result to be out of range. For your convenience, Nudge silently coerces the output values in order to output valid tags. 58 | 59 | Operations 60 | ---------- 61 | 62 | - **Add:** Adds the supplied values to the tag fields (Default: 0) 63 | - **Multiply:** Multiplies the tag fields with the supplied values (Default: 1) 64 | - **Power:** Exponentiates the tag fields with the supplied values (Default 1) 65 | - **Set:** Sets the tag fields to the supplied values (Defaults: tag/tag field dependent) 66 | - **Cycle:** Cycles through a defined set of values. Values must be in the format *[Set1],[Set2],[Set3]*, e.g. *[100,100],[500,500],[1000,1000]* for *\fad* 67 | - **Auto Cycle:** Cycles through the states of tags that only define a set amount of states (*\q*, *\an*). The *value* field is ignored. 68 | - **Set Default:** Sets the tag fields to their default values according to the style of the line. The *value* field is ignored. 69 | - **Toggle:** Switches on/off type tags (*\i*,*\u*...) between 1 and 0. The *value* field is ignored. 70 | - **Add HSV:** modifies RGB values of color tags in the HSV domain. Values must be supplied as *Hue,Saturation,Value*. Hue takes an angle, while Saturation and Value must be supplied in range 0..1. 71 | - **Align Up/Down/Left/Right:** changes the alignment (*\an*) of a line stepwise in the specified direction. Example: *Align Up* changes *\an2* to *\an5*, *\an1* to *\an7*, but doesn't do anything for *\an8*. Set the *first value* to *true* to make Nudge maintain the appearance of the line by compensating the alignment change with *\pos* and (if required) *\org* adjustments. 72 | - **Append/Prepend:**: appends/prepends the specified string to string type tags (*\fs*, *\r*) 73 | - **Replace**: replaces in string type tags using regular expressions **(NOT lua expressions)**. First value is the string or pattern to match, second value the replacement string. 74 | - **Invert Clip**: Changes *\clip* to *\iclip* and vice versa 75 | - **Convert To Drawing**: Converts clips to drawings. Set the *first value* to *true* to keep the clips and the *second value* to *true* to keep the previous position. 76 | - **Copy**: Copies the tag to the clipboard 77 | - **Paste Into**: Pastes the configured tag from the clipboard into the configured tag section. Global tags will always be written to the first tag section (if the first tag section is not a tag section, one will be created), overwriting already present global tags. If there are multiple tags in the clipboard, only the ones configured will be pasted (if you want to paste all tags, use the *Any Tag* option). 78 | - **Paste Over**: Works like the **Paste Into** operation, however it also overwrites any non-global tags encountered. Only creates new tags if none were found and overwritten. 79 | - **Set/Unset/Toggle Comment**: Turns a Dialogue line into a comment or vice versa. 80 | 81 | Supported Operations by Tag 82 | --------------------------- 83 | 84 | Target | Add, Mul, Pow | Set | Def | Cycl | ACycl | Toggle | HSV | Align | Rep, App, Prep | InvClp | 85 | -----------|---------------|-----|-----|------|-------|--------|-----|-------|----------------|--------- 86 | \1a | ✔ | ✔ | ✔ | ✔ | | | | | | | 87 | \2a | ✔ | ✔ | ✔ | ✔ | | | | | | | 88 | \3a | ✔ | ✔ | ✔ | ✔ | | | | | | | 89 | \4a | ✔ | ✔ | ✔ | ✔ | | | | | | | 90 | \1c | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 91 | \2c | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 92 | \3c | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 93 | \4c | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 94 | \alpha | ✔ | ✔ | ✔ | ✔ | | | | | | | 95 | \an | ✔ | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | 96 | \b | ✔ | ✔ | ✔ | ✔ | | ✔ | | | | | 97 | \be | ✔ | ✔ | ✔ | ✔ | | | | | | | 98 | \blur | ✔ | ✔ | ✔ | ✔ | | | | | | | 99 | \bord | ✔ | ✔ | ✔ | ✔ | | | | | | | 100 | \c | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 101 | \clip | ✔ | | ✔ | | | | | | | ✔ | 102 | \clip(Vect) | ✔ | | ✔ | | | | | | | ✔ | 103 | \clip(Rect) | ✔ | ✔ | ✔ | ✔ | | | | | | ✔ | 104 | \fad | ✔ | ✔ | ✔ | ✔ | | | | | | | 105 | \fade | ✔ | ✔ | ✔ | ✔ | | | | | | | 106 | \fax | ✔ | ✔ | ✔ | ✔ | | | | | | | 107 | \fay | ✔ | ✔ | ✔ | ✔ | | | | | | | 108 | \fe | | | | | | | | | | | 109 | \fn | | ✔ | ✔ | ✔ | | | | | ✔ | | 110 | \frx | ✔ | ✔ | ✔ | ✔ | | | | | | | 111 | \fry | ✔ | ✔ | ✔ | ✔ | | | | | | | 112 | \frz | ✔ | ✔ | ✔ | ✔ | | | | | | | 113 | \fs | ✔ | ✔ | ✔ | ✔ | | | | | | | 114 | \fscx | ✔ | ✔ | ✔ | ✔ | | | | | | | 115 | \fscy | ✔ | ✔ | ✔ | ✔ | | | | | | | 116 | \fsp | ✔ | ✔ | ✔ | ✔ | | | | | | | 117 | \k | ✔ | ✔ | ✔ | ✔ | | | | | | | 118 | \K | ✔ | ✔ | ✔ | ✔ | | | | | | | 119 | \kf | ✔ | ✔ | ✔ | ✔ | | | | | | | 120 | \ko | ✔ | ✔ | ✔ | ✔ | | | | | | | 121 | \i | | ✔ | ✔ | | | ✔ | | | | | 122 | \iclip | ✔ | | ✔ | | | | | | | ✔ | 123 | \iclip(Vect)| ✔ | | ✔ | | | | | | | ✔ | 124 | \iclip(Rect)| ✔ | ✔ | ✔ | ✔ | | | | | | ✔ | 125 | \move | ✔ | ✔ | ✔ | ✔ | | | | | | | 126 | \org | ✔ | ✔ | ✔ | ✔ | | | | | | | 127 | \pos | ✔ | ✔ | ✔ | ✔ | | | | | | | 128 | \q | | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | 129 | \r | | ✔ | ✔ | ✔ | | | | | ✔ | | 130 | \t | | | | | | | | | | | 131 | \u | | ✔ | ✔ | | | ✔ | | | | | 132 | \xbord | ✔ | ✔ | ✔ | ✔ | | | | | | | 133 | \ybord | ✔ | ✔ | ✔ | ✔ | | | | | | | 134 | \xshad | ✔ | ✔ | ✔ | ✔ | | | | | | | 135 | \yshad | ✔ | ✔ | ✔ | ✔ | | | | | | | 136 | | | | | | | | | | | | 137 | Alphas | ✔ | ✔ | ✔ | ✔ | | | | | | | 138 | Clips | ✔ | | ✔ | | | | | | | ✔ | 139 | Clips (Vect)| ✔ | | ✔ | | | | | | | ✔ | 140 | Clips (Rect)| ✔ | ✔ | ✔ | ✔ | | | | | | ✔ | 141 | Colors | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 142 | Fades | ✔ | ✔ | ✔ | ✔ | | | | | | | 143 | Prim. Color | ✔ | ✔ | ✔ | ✔ | | | ✔ | | | | 144 | | | | | | | | | | | | 145 | Line | | | | | | | | | | | 146 | 147 | 148 | 149 | Target | ConvToDrawing | Copy/Paste | Comment | | | | | | | | 150 | ------------|---------------|------------|---------|------|-------|--------|-----|-------|----------------|--------- 151 | \1a | | ✔ | | | | | | | | | 152 | \2a | | ✔ | | | | | | | | | 153 | \3a | | ✔ | | | | | | | | | 154 | \4a | | ✔ | | | | | | | | | 155 | \1c | | ✔ | | | | | | | | | 156 | \2c | | ✔ | | | | | | | | | 157 | \3c | | ✔ | | | | | | | | | 158 | \4c | | ✔ | | | | | | | | | 159 | \alpha | | ✔ | | | | | | | | | 160 | \an | | ✔ | | | | | | | | | 161 | \b | | ✔ | | | | | | | | | 162 | \be | | ✔ | | | | | | | | | 163 | \blur | | ✔ | | | | | | | | | 164 | \bord | | ✔ | | | | | | | | | 165 | \c | | ✔ | | | | | | | | | 166 | \clip | ✔ | ✔ | | | | | | | | | 167 | \clip(Vect) | ✔ | ✔ | | | | | | | | | 168 | \clip(Rect) | ✔ | ✔ | | | | | | | | | 169 | \fad | | ✔ | | | | | | | | | 170 | \fade | | ✔ | | | | | | | | | 171 | \fax | | ✔ | | | | | | | | | 172 | \fay | | ✔ | | | | | | | | | 173 | \fe | | ✔ | | | | | | | | | 174 | \fn | | ✔ | | | | | | | | | 175 | \frx | | ✔ | | | | | | | | | 176 | \fry | | ✔ | | | | | | | | | 177 | \frz | | ✔ | | | | | | | | | 178 | \fs | | ✔ | | | | | | | | | 179 | \fscx | | ✔ | | | | | | | | | 180 | \fscy | | ✔ | | | | | | | | | 181 | \fsp | | ✔ | | | | | | | | | 182 | \k | | ✔ | | | | | | | | | 183 | \K | | ✔ | | | | | | | | | 184 | \kf | | ✔ | | | | | | | | | 185 | \ko | | ✔ | | | | | | | | | 186 | \i | | ✔ | | | | | | | | | 187 | \iclip | ✔ | ✔ | | | | | | | | | 188 | \iclip(Vect)| ✔ | ✔ | | | | | | | | | 189 | \iclip(Rect)| ✔ | ✔ | | | | | | | | | 190 | \move | | ✔ | | | | | | | | | 191 | \org | | ✔ | | | | | | | | | 192 | \pos | | ✔ | | | | | | | | | 193 | \q | | ✔ | | | | | | | | | 194 | \r | | | | | | | | | | | 195 | \t | | ✔ | | | | | | | | | 196 | \u | | ✔ | | | | | | | | | 197 | \xbord | | ✔ | | | | | | | | | 198 | \ybord | | ✔ | | | | | | | | | 199 | \xshad | | ✔ | | | | | | | | | 200 | \yshad | | ✔ | | | | | | | | | 201 | | | | | | | | | | | | 202 | Alphas | | ✔ | | | | | | | | | 203 | Clips | ✔ | ✔ | | | | | | | | | 204 | Clips (Vect)| ✔ | ✔ | | | | | | | | | 205 | Clips (Rect)| ✔ | ✔ | | | | | | | | | 206 | Colors | | ✔ | | | | | | | | | 207 | Fades | | ✔ | | | | | | | | | 208 | Prim. Color | | ✔ | | | | | | | | | 209 | | | | | | | | | | | | 210 | Line | | ✔ | ✔ | | | | | | | | 211 | 212 | ------------------------------- 213 | 214 | Shake It 215 | ============================== 216 | 217 | tbd -------------------------------------------------------------------------------- /l0.ASSWipe.moon: -------------------------------------------------------------------------------- 1 | export script_name = "ASSWipe" 2 | export script_description = "Performs script cleanup, removes unnecessary tags and lines." 3 | export script_version = "0.5.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.ASSWipe" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | version = DependencyControl{ 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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.ConfigHandler", version: "1.1.4", 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.5.0", url: "https://github.com/TypesettingTools/Functional", 18 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 19 | {"SubInspector.Inspector", version: "0.7.2", url: "https://github.com/TypesettingTools/SubInspector", 20 | feed: "https://raw.githubusercontent.com/TypesettingTools/SubInspector/master/DependencyControl.json"}, 21 | "json" 22 | } 23 | } 24 | LineCollection, ConfigHandler, ASS, Functional, SubInspector, json = version\requireModules! 25 | ffms, min, concat, sort = aegisub.frame_from_ms, math.min, table.concat, table.sort 26 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 27 | logger = version\getLogger! 28 | 29 | reportMsg = [[ 30 | Done. Processed %d lines in %d seconds. 31 | — Cleaned %d lines (%d%%) 32 | — Removed %d invisible lines (%d%%) 33 | — Combined %d consecutive identical lines (%d%%) 34 | — Filtered %d clips and %d occurences of junk data 35 | — Purged %d invisible contours (%d in drawings, %d in clips) 36 | — Failed to purge %d invisible contours due to rendering inconsistencies 37 | — Converted %d drawings/clips to floating-point 38 | — Filtered %d records of extra data 39 | — Total space saved: %.2f KB 40 | ]] 41 | 42 | hints = { 43 | cleanLevel: [[ 44 | 0: no cleaning 45 | 1: remove empty tag sections 46 | 2: deduplicate tags inside sections 47 | 3: deduplicate tags globally, 48 | 4: remove tags matching the style defaults and otherwise ineffective tags]] 49 | tagsToKeep: "Don't remove these tags even if they match the style defaults for the line." 50 | filterClips: "Removes clips that don't affect the rendered output." 51 | removeInvisible: "Deletes lines that don't generate any visible output." 52 | combineLines: "Merges non-animated lines that render to an identical result and have consecutive times (without overlaps or gaps)." 53 | removeJunk: "Removes any 'in-line comments' and things not starting with a \\ from tag sections." 54 | scale2float: "Converts drawings and clips with a scale parameter to a floating-point representation." 55 | tagSortOrder: "Determines the order cleaned tags will be ordered inside a tag section. Resets always go first, transforms last." 56 | fixDrawings: "Removes extraneous ordinates from broken drawings to make them parseable. May or may not changed the rendered output." 57 | purgeContoursDraw: "Removes all contours of a drawing that are not visible on the canvas." 58 | purgeContoursClip: "Removes all contours of a clip that do not affect the appearance of the line." 59 | stripComments: "Removes any comments encapsulated in {curly brackets}." 60 | purgeContoursIgnoreHashMismatch: "Removes invisible contours even if it causes a SubInspector hash mismatch when comparing the result to the original line. This may or may not visually affect your drawing, so never use this option unsupervised!" 61 | } 62 | 63 | defaultSortOrder = [[ 64 | \an, \pos, \move, \org, \fscx, \fscy, \frz, \fry, \frx, \fax, \fay, \fn, \fs, \fsp, \b, \i, \u, \s, \bord, \xbord, \ybord, 65 | \shad, \xshad, \yshad, \1c, \2c, \3c, \4c, \alpha, \1a, \2a, \3a, \4a, \blur, \be, \fad, \fade, clip_rect, iclip_rect, 66 | clip_vect, iclip_vect, \q, \p, \k, \kf, \K, \ko, junk, unknown 67 | ]] 68 | karaokeTags = table.concat table.pluck(table.filter(ASS.tagMap, (tag) -> tag.props.karaoke), "overrideName"), ", " 69 | 70 | -- to be moved into ASSFoundation.Functional 71 | sortWithKeys = (tbl, comparator) -> 72 | -- shellsort written by Rici Lake 73 | -- c/p from http://lua-users.org/wiki/LuaSorting, with index argument added to comparator 74 | incs = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 } 75 | n = #tbl 76 | for h in *incs 77 | for i = h+1, n 78 | a = tbl[i] 79 | for j = i-h, 1, -h 80 | b = tbl[j] 81 | break unless comparator a, b, i, j 82 | tbl[i] = b 83 | i = j 84 | tbl[i] = a 85 | return tbl 86 | 87 | 88 | -- returns if we can merge line b into a while still maintain b's layer order 89 | isMergeable = (a, b, linesByFrame) -> 90 | for i = b.firstFrame, b.lastFrame 91 | group = linesByFrame[i] 92 | local pos 93 | 94 | unless group.sorted 95 | -- ensure line group is sorted by layer blending order 96 | sortWithKeys group, (x, y, i, j) -> 97 | pos or= i if x == b 98 | return x.layer < y.layer or x.layer == y.layer and i < j 99 | 100 | group.sorted = true 101 | 102 | -- get line position in blending order 103 | pos or= i for i, v in ipairs group when v == b 104 | 105 | -- as b is merged into a, it gets a's layer number 106 | -- so we can only merge if the new layer number does not change the blending order in any of the frames b is visible in 107 | lower = group[pos-1] 108 | return false unless (not lower or a.layer > lower.layer or a.layer == lower.layer and a.number > lower.number) 109 | 110 | higher = group[pos+1] 111 | return false unless (not higher or a.layer < higher.layer or a.layer == higher.layer and a.number < higher.number) 112 | 113 | return true 114 | 115 | mergeLines = (lines, start, cmbCnt, bytes, linesByFrame) -> 116 | -- queue merged lines for deletion and collect statistics 117 | if lines[start].merged 118 | return lines[start], cmbCnt+1, bytes + #lines[start].raw + 1 119 | 120 | -- merge applicable lines into first mergeable by extending its end time 121 | -- then mark all merged lines 122 | merged = lines[start] 123 | for i=start+1,lines.n 124 | line = lines[i] 125 | break if line.merged or line.start_time != merged.end_time or not isMergeable merged, line, linesByFrame 126 | lines[i].merged = true 127 | merged.end_time = lines[i].end_time 128 | 129 | -- update lines by frame index 130 | for f = line.firstFrame, line.lastFrame 131 | group = linesByFrame[f] 132 | pos = i for i, v in ipairs group when v == line 133 | group[pos] = merged 134 | 135 | return nil, cmbCnt, bytes 136 | 137 | 138 | removeInvisibleContoursOptCollectBounds = (contour, _, sectionContourIndex, sliceContourIndex, _, sliceRawContours, sliceSize, linePre, linePost, isAnimated, boundsBatch) -> 139 | prevContours = concat sliceRawContours, " ", 1, sliceContourIndex-1 140 | nextContours = sliceContourIndex <= sliceSize and concat(sliceRawContours, " ", sliceContourIndex+1) or "" 141 | text = "#{linePre}#{prevContours}#{(#prevContours == 0 or #nextContours == 0) and "" or " "}#{nextContours}#{linePost}" 142 | boundsBatch\add contour.parent.parent, text, sectionContourIndex, isAnimated 143 | rawContour = sliceRawContours[sliceContourIndex] 144 | 145 | removeInvisibleContoursOptPurge = (contour, contours, i, _, _, allBounds, orgBounds) -> 146 | bounds, allBounds[i] = allBounds[i] 147 | return false if orgBounds\equal bounds 148 | 149 | removeInvisibleContoursOpt = (section, orgBounds) -> 150 | cutOff, ass, contourCnt = false, section.parent, #section.contours 151 | 152 | sliceSize = math.ceil 10e4 / contourCnt 153 | if contourCnt > 100 154 | logger\hint "Cleaning complex drawing with %d contours (slice size: %s)...", contourCnt, sliceSize 155 | 156 | selectSurroundingSections = (sect, _, _, _, toTheLeft) -> 157 | if toTheLeft 158 | cutOff = true if section == sect 159 | else 160 | if section == sect 161 | cutOff = false 162 | return false 163 | 164 | return not cutOff 165 | 166 | 167 | lineStringPre, drwState = ass\getString nil, nil, selectSurroundingSections, false, true 168 | lineStringPost = ass\getString nil, drwState, selectSurroundingSections, false, false 169 | 170 | allBounds, sliceContours, sliceStartIndex = {} 171 | isAnimated = ass\isAnimated! 172 | 173 | for sliceStartIndex = 1, contourCnt, sliceSize 174 | sliceEndIndex = min sliceStartIndex+sliceSize-1, contourCnt 175 | sliceContours = [cnt\getTagParams! for cnt in *section.contours[sliceStartIndex, sliceEndIndex]] 176 | boundsBatch = ASS.LineBoundsBatch! 177 | 178 | section\callback removeInvisibleContoursOptCollectBounds, sliceStartIndex, sliceEndIndex, nil, nil, 179 | sliceContours, sliceSize, lineStringPre, lineStringPost, isAnimated, boundsBatch 180 | 181 | boundsBatch\run true, allBounds 182 | lineStringPre ..= concat sliceContours, " " 183 | boundsBatch = nil 184 | collectgarbage! 185 | 186 | _, purgeCnt = section\callback removeInvisibleContoursOptPurge, nil, nil, nil, nil, allBounds, orgBounds 187 | return purgeCnt 188 | 189 | stripComments = () -> false 190 | 191 | process = (sub, sel, res) -> 192 | ASS.config.fixDrawings = res.fixDrawings 193 | lines = LineCollection sub, sel 194 | linesToDelete, delCnt, linesToCombine, cmbCnt, lineCnt, debugError = {}, 0, {}, 0, #lines.lines, false 195 | tagNames = res.filterClips and util.copy(ASS.tagNames.clips) or {} 196 | tagNames[#tagNames+1] = res.removeJunk and "junk" 197 | stats = { bytes: 0, junk: 0, clips: 0, start: os.time!, cleaned: 0, 198 | scale2float: 0, contoursDraw: 0, contoursDrawSkipped: 0, contoursClip: 0, extra: 0 } 199 | linesByFrame = {} 200 | 201 | -- create proper tag name lists from user input which may be override tag names or mixed 202 | res.tagsToKeep = ASS\getTagNames string.split res.tagsToKeep, ",%s", nil, false 203 | res.tagSortOrder = ASS\getTagNames string.split res.tagSortOrder, ",%s", nil, false 204 | res.mergeConsecutiveExcept = ASS\getTagNames string.split res.mergeConsecutiveExcept, ",%s", nil, false 205 | res.extraDataFilter = string.split res.extraDataFilter, ",%s", nil, false 206 | 207 | callback = (lines, line, i) -> 208 | aegisub.cancel! if aegisub.progress.is_cancelled! 209 | aegisub.progress.task "Cleaning %d of %d lines..."\format i, lineCnt if i%10==0 210 | aegisub.progress.set 100*i/lineCnt 211 | 212 | unless line.styleRef 213 | logger\warn "WARNING: Line #%d is using undefined style '%s', skipping...\n— %s", i, line.style, line.text 214 | return 215 | 216 | -- filter extra data 217 | if line.extra and res.extraDataMode != "Keep all" 218 | removed, r = switch res.extraDataMode 219 | when "Remove all" 220 | removed = line.extra 221 | line.extra = nil 222 | removed, table.length removed 223 | when "Remove all except" 224 | table.removeKeysExcept line.extra, res.extraDataFilter 225 | when "Keep all except" 226 | table.removeKeys line.extra, res.extraDataFilter 227 | stats.extra += r 228 | 229 | success, data = pcall ASS\parse, line 230 | unless success 231 | logger\warn "Couldn't parse line #%d: %s", i, data 232 | return 233 | 234 | -- it is essential to run SubInspector on a ASSFoundation-built line (rather than the original) 235 | -- because ASSFoundation rounds tag values to a sane precision, which is not visible but 236 | -- will produce a hash mismatch compared to the original line. However we must avoid that to 237 | -- not trigger the ASSWipe bug detection 238 | orgText, oldBounds = line.text, data\getLineBounds false, true 239 | orgTextParsed = oldBounds.rawText 240 | orgTagTypes = ["#{tag.__tag.name}(#{table.concat({tag\getTagParams!}, ",")})" for tag in *data\getTags!] 241 | 242 | 243 | removeInvisibleContours = (contour) -> 244 | contour.disabled = true 245 | if oldBounds\equal data\getLineBounds! 246 | if contour.parent.class == ASS.Section.Drawing 247 | stats.contoursDraw += 1 248 | else stats.contoursClip += 1 249 | return false 250 | contour.disabled = false 251 | 252 | 253 | -- remove invisible lines 254 | if res.removeInvisible and oldBounds.w == 0 255 | stats.bytes += #line.raw + 1 256 | delCnt += 1 257 | linesToDelete[delCnt], line.ASS = line 258 | return 259 | 260 | 261 | purgedContourCount = 0 262 | if res.purgeContoursDraw or res.scale2float 263 | cb = (section) -> 264 | -- remove invisible contours from drawings 265 | if res.purgeContoursDraw 266 | purgedContourCount = removeInvisibleContoursOpt section, oldBounds 267 | -- un-scale drawings 268 | if res.scale2float and section.scale > 1 269 | section.scale\set 1 270 | stats.scale2float += 1 271 | 272 | data\callback cb, ASS.Section.Drawing 273 | if purgedContourCount > 0 274 | if res.purgeContoursDraw and not res.purgeContoursIgnoreHashMismatch and not data\getLineBounds!\equal oldBounds 275 | line.text = orgText 276 | data = ASS\parse line 277 | stats.contoursDrawSkipped += purgedContourCount 278 | else 279 | stats.contoursDraw += purgedContourCount 280 | oldBounds = data\getLineBounds! if res.purgeContoursIgnoreHashMismatch 281 | 282 | -- pogressively build a table of visible lines by frame 283 | -- which is required to check mergeability of consecutive identical lines 284 | if res.combineLines 285 | line.firstFrame = ffms line.start_time 286 | line.lastFrame = -1 + ffms line.end_time 287 | for i = line.firstFrame, line.lastFrame 288 | lbf = linesByFrame[i] 289 | if lbf 290 | lbf[lbf.n+1] = line 291 | lbf.n += 1 292 | else linesByFrame[i] = {line, n: 1} 293 | 294 | -- collect lines to combine 295 | unless oldBounds.animated 296 | ltc = linesToCombine[oldBounds.firstHash] 297 | if ltc 298 | ltc[ltc.n+1] = line 299 | ltc.n += 1 300 | else linesToCombine[oldBounds.firstHash] = {line, n: 1} 301 | 302 | mergeConsecutiveTagSections = if not res.mergeConsecutive 303 | false 304 | elseif #res.mergeConsecutiveExcept == 0 305 | true 306 | else 307 | exceptions = list.makeSet res.mergeConsecutiveExcept 308 | (sourceSection, targetSection) -> 309 | predicate = (tag) -> exceptions[tag.__tag.name] 310 | not table.find(sourceSection.tags, predicate) and not table.find targetSection.tags, predicate 311 | -- clean tags 312 | data\cleanTags res.cleanLevel, mergeConsecutiveTagSections, res.tagsToKeep, res.tagSortOrder 313 | newBounds = data\getLineBounds! 314 | 315 | if res.stripComments 316 | data\stripComments! 317 | --data\callback stripComments, ASS.Section.Comment 318 | 319 | if res.filterClips or res.removeJunk 320 | data\modTags tagNames, (tag) -> 321 | -- remove junk 322 | if tag.class == ASS.Tag.Unknown 323 | stats.junk += 1 324 | return false 325 | 326 | if tag.class == ASS.Tag.ClipVect 327 | -- un-scale clips 328 | if res.scale2float and tag.scale>1 329 | tag.scale\set 1 330 | stats.scale2float += 1 331 | -- purge ineffective contours from clips 332 | if res.purgeContoursClip 333 | tag\callback removeInvisibleContours 334 | 335 | -- filter clips 336 | tag.disabled = true 337 | if data\getLineBounds!\equal newBounds 338 | stats.clips += 1 339 | return false 340 | tag.disabled = false 341 | 342 | data\commit nil, res.cleanLevel == 0 343 | -- reclaim some memory 344 | line.ASS, line.undoText = nil 345 | 346 | if orgText != line.text 347 | if not newBounds\equal oldBounds 348 | debugError = true 349 | logger\warn "Cleaning affected output on line #%d, rolling back...", line.humanizedNumber 350 | logger\warn "—— Before: %s\n—— Parsed: %s\n—— After: %s\n—— Style: %s\n—— Tags: %s", orgText, orgTextParsed, line.text, line.styleRef.name, table.concat orgTagTypes, "; " 351 | logger\warn "—— Hash Before: %s (%s); Hash After: %s (%s)\n", 352 | oldBounds.firstHash, oldBounds.animated and "animated" or "static", 353 | newBounds.firstHash, newBounds.animated and "animated" or "static" 354 | line.text = orgText 355 | elseif #line.text < #orgText 356 | stats.cleaned += 1 357 | stats.bytes += #orgText - #line.text 358 | 359 | aegisub.cancel! if aegisub.progress.is_cancelled! 360 | lines\runCallback callback, true 361 | 362 | -- sort lines which are to be combined by time 363 | sortFunc = (a, b) -> 364 | return true if a.start_time < b.start_time 365 | return false if a.start_time > b.start_time 366 | return true if a.layer < b.layer 367 | return false if a.layer > b.layer 368 | return true if a.number < b.number 369 | return false 370 | 371 | linesToCombineSorted, l = {}, 1 372 | for _, group in pairs linesToCombine 373 | continue if group.n < 2 374 | sort group, sortFunc 375 | linesToCombineSorted[l] = group 376 | l += 1 377 | sort linesToCombineSorted, (a, b) -> sortFunc a[1], b[1] 378 | 379 | -- combine lines 380 | for group in *linesToCombineSorted 381 | for j=1, group.n 382 | linesToDelete[delCnt+cmbCnt+1], cmbCnt, stats.bytes = mergeLines group, j, cmbCnt, stats.bytes, linesByFrame 383 | 384 | lines\replaceLines! 385 | lines\deleteLines linesToDelete 386 | 387 | logger\warn json.encode {Styles: lines.styles, Configuration: res} if debugError 388 | logger\warn reportMsg, lineCnt, os.time!-stats.start, stats.cleaned, 100*stats.cleaned/lineCnt, 389 | delCnt, 100*delCnt/lineCnt, cmbCnt, 100*cmbCnt/lineCnt, stats.clips, stats.junk, 390 | stats.contoursClip+stats.contoursDraw, stats.contoursDraw, stats.contoursClip, stats.contoursDrawSkipped, 391 | stats.scale2float, stats.extra, stats.bytes/1000 392 | 393 | if debugError 394 | logger\warn [[However, ASSWipe possibly encountered bugs while cleaning. 395 | Affected lines have been rolled back to their previous state, so your script is most likely fine. 396 | Please copy the whole log window contents and send them to line0.]] 397 | 398 | 399 | return lines\getSelection! 400 | 401 | 402 | showDialog = (sub, sel, res) -> 403 | dlg = { 404 | main: { 405 | removeInvisible: class: "checkbox", x: 0, y: 0, width: 2, height: 1, value: true, config: true, label: "Remove invisible lines", hint: hints.removeInvisible 406 | combineLines: class: "checkbox", x: 0, y: 1, width: 2, height: 1, value: true, config: true, label: "Combine consecutive identical lines", hint: hints.combineLines 407 | mergeConsecutive: class: "checkbox", x: 2, y: 0, width: 12, height: 1, value: true, config: true, label: "Merge consecutive tag sections unless it contains any of:", hint: hints.mergeConsecutive 408 | mergeConsecutiveExcept: class: "textbox", x: 2, y: 1, width: 12, height: 2, value: karaokeTags, config: true, hint: hints.mergeConsecutiveExcept 409 | 410 | cleanLevelLabel: class: "label", x: 0, y: 4, width: 1, height: 1, label: "Tag cleanup level: " 411 | cleanLevel: class: "intedit", x: 1, y: 4, width: 1, height: 1, min: 0, max: 4, value: 4, config: true, hint: hints.cleanLevel 412 | tagsToKeepLabel: class: "label", x: 4, y: 4, width: 1, height: 1, label: "Keep default tags: " 413 | tagsToKeep: class: "textbox", x: 4, y: 5, width: 10, height: 2, value: "\\pos", config:true, hint: hints.tagsToKeep 414 | tagSortOrderLabel: class: "label", x: 4, y: 7, width: 1, height: 1, label: "Tag sort order: " 415 | stripComments: class: "checkbox", x: 0, y: 5, width: 2, height: 1, value: true, config: true, label: "Strip comments", hint: hints.stripComments 416 | removeJunk: class: "checkbox", x: 0, y: 6, width: 2, height: 1, value: true, config: true, label: "Remove junk from tag sections", hint: hints.removeJunk 417 | tagSortOrder: class: "textbox", x: 4, y: 8, width: 10, height: 3, value: defaultSortOrder, config: true, hint: hints.tagSortOrder 418 | 419 | filterClips: class: "checkbox", x: 0, y: 11, width: 2, height: 1, value: true, config: true, label: "Filter clips", hint: hints.filterClips 420 | scale2float: class: "checkbox", x: 0, y: 12, width: 2, height: 1, value: true, config: true, label: "Un-scale drawings and clips", hint: hints.scale2float 421 | fixDrawings: class: "checkbox", x: 0, y: 13, width: 2, height: 1, value: false, config: true, label: "Try to fix broken drawings", hint: hints.fixDrawings 422 | purgeContoursLabel: class: "label", x: 0, y: 14, width: 2, height: 1, label: "Purge invisible contours: " 423 | purgeContoursDraw: class: "checkbox", x: 4, y: 14, width: 3, height: 1, value: false, config: true, label: "from drawings", hint: hints.purgeContoursDraw 424 | purgeContoursClip: class: "checkbox", x: 7, y: 14, width: 6, height: 1, value: false, config: true, label: "from clips", hint: hints.purgeContoursClip 425 | purgeContoursIgnoreHashMismatch: class: "checkbox", x: 4, y: 15, width: 9, height: 1, value: false, config: true, label: "ignore rendering inconsistencies", hint: hints.purgeContoursIgnoreHashMismatch 426 | extraDataLabel: class: "label", x: 0, y: 17, width: 1, height: 1, label: "Filter extra data: " 427 | extraDataMode: class: "dropdown", x: 1, y: 17, width: 1, height: 1, value: "Keep All", config: true, items: {"Keep all", "Remove all", "Keep all except", "Remove all except"}, hint: hints.extraData 428 | extraDataFilter: class: "textbox", x: 4, y: 17, width: 10, height: 3, value: "", config: true, hint: hints.extraData 429 | quirksModeLabel: class: "label", x: 0, y: 21, width: 2, height: 1, label: "Quirks mode: " 430 | quirksMode: class: "dropdown", x: 4, y: 21, width: 2, height: 1, value: "VSFilter", config: true, items: [k for k, v in pairs ASS.Quirks], hint: hints.quirksMode 431 | } 432 | } 433 | options = ConfigHandler dlg, version.configFile, false, script_version, version.configDir 434 | options\read! 435 | options\updateInterface "main" 436 | btn, res = aegisub.dialog.display dlg.main 437 | if btn 438 | options\updateConfiguration res, "main" 439 | options\write! 440 | ASS.config.quirks = ASS.Quirks[res.quirksMode] 441 | process sub, sel, res 442 | 443 | version\registerMacro showDialog, -> 444 | if aegisub.project_properties!.video_file == "" 445 | return false, "A video must be loaded to run #{script_name}." 446 | else return true, script_description 447 | -------------------------------------------------------------------------------- /l0.CascadeTransforms.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Cascade Transforms" 2 | export script_description = "Changes transforms in a line to be transformed in a consecutive fashion, with the transform time being split evenly." 3 | export script_version = "0.1.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.CascadeTransforms" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | 9 | rec = DependencyControl{ 10 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/DependencyControl.json", 11 | { 12 | {"a-mo.LineCollection", version: "1.0.1", url: "https://github.com/TypesettingTools/Aegisub-Motion", 13 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 14 | {"l0.ASSFoundation", version: "0.2.2", url: "https://github.com/TypesettingTools/ASSFoundation", 15 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"} 16 | } 17 | } 18 | 19 | LineCollection, ASS = rec\requireModules! 20 | 21 | cascadeTransforms = (sub, sel) -> 22 | lines = LineCollection sub, sel 23 | lines\runCallback (lines, line, i) -> 24 | data = ASS\parse line 25 | transforms = data\getTags "transform" 26 | return if #transforms == 0 27 | interval = line.duration / #transforms 28 | start = 0 29 | for t in *transforms 30 | t.startTime\set start 31 | start += interval 32 | t.endTime\set start 33 | data\commit! 34 | 35 | lines\replaceLines! 36 | 37 | rec\registerMacro cascadeTransforms 38 | -------------------------------------------------------------------------------- /l0.HighlightSubstring.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Highlight Substring" 2 | export script_description = "Highlights a substring at a given index in a line by underlaying a colored rectangle." 3 | export script_version = "0.1.1" 4 | export script_author = "line0" 5 | export script_namespace = "l0.HighlightSubstring" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | 9 | rec = DependencyControl{ 10 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 15 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"} 16 | } 17 | } 18 | 19 | LineCollection, ASS = rec\requireModules! 20 | logger = rec\getLogger! 21 | 22 | highlightSubstring = (sub, sel, res) -> 23 | lines = LineCollection sub, sel 24 | lines\runCallback (lines, line, i) -> 25 | -- outline should be below original, line must be at least at layer 1 26 | line.layer = math.min line.layer, 1 27 | data = ASS\parse line 28 | 29 | local splitLine 30 | if res.start <= 1 31 | -- can't split a line at position <= 1 32 | splitLine = (data\splitAtIndexes res.end+1)[1] 33 | else 34 | splitLine = (data\splitAtIndexes {res.start, res.end+1})[2] 35 | 36 | bounds = splitLine.ASS\getLineBounds! 37 | box, tags = ASS.Section.Drawing!, ASS.Section.Tag { 38 | ASS\createTag("position", 0, 0), 39 | ASS\createTag("align", 7), 40 | ASS\createTag("color1", 0, 0, 255), 41 | ASS\createTag("alpha", 127), 42 | ASS\createTag("outline", 0), 43 | ASS\createTag("shadow", 0) 44 | } 45 | 46 | if bounds.w > 0 47 | box\drawRect bounds[1], bounds[2] 48 | lines\addLine ASS\createLine{{tags, box}, data, layer: line.layer - 1} 49 | 50 | lines\replaceLines! 51 | lines\insertLines! 52 | 53 | showDialog = (sub, sel) -> 54 | btn, res = aegisub.dialog.display { 55 | {class: "label", label:"Start Index:", x: 0, y: 0, width: 1, height: 1}, 56 | {class: "intedit", name: "start", x: 1, y: 0, width: 1, height: 1, value: 1, min: 0}, 57 | {class: "label", label:"End Index:", x: 0, y: 1, width: 1, height: 1}, 58 | {class: "intedit", name: "end", x: 1, y: 1, width: 1, height: 1, value: 1, min: 0} 59 | } 60 | 61 | -- idiot validation 62 | res.end = math.max(res.start, res.end) 63 | 64 | highlightSubstring sub, sel, res if btn 65 | 66 | 67 | rec\registerMacro showDialog 68 | -------------------------------------------------------------------------------- /l0.InsertLineBreaks.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Insert Line Breaks" 2 | export script_description = "Inserts hard line breaks after n characters, but tries to avoid breaking up words." 3 | export script_version = "0.2.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.InsertLineBreaks" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depCtrl = DependencyControl { 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | {"l0.Functional", version: "0.5.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 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 21 | logger = depCtrl\getLogger! 22 | 23 | insertLineBreaks = (sub, sel, res) -> 24 | lines = LineCollection sub, sel 25 | curCnt, expr = res.charLimit, re.compile "\\s(?!.*\\s)" 26 | lines\runCallback (lines, line) -> 27 | data = ASS\parse line 28 | textSectionCb = (section) -> 29 | j, n, len, split = 1, 1, unicode.len(section.value), {} 30 | while j <= len 31 | splitLen = math.min curCnt, len-j+1 32 | split[n] = unicode.sub section.value, j, j+splitLen-1 33 | j += curCnt 34 | if splitLen - curCnt == 0 35 | curCnt = res.charLimit 36 | -- if the next character is a whitespace character, replace it with a line break 37 | if re.match unicode.sub(section.value, j, j), "\\s" 38 | j += 1 39 | split[n+1] = "\\N" 40 | -- if it isn't, find the last whitespace character in our last <= n chars section 41 | else 42 | matches = expr\find split[n] 43 | -- found one -> place the line break there and add the character count after that position 44 | -- to the char count of the next section 45 | if matches 46 | pos = matches[1].last 47 | split[n], split[n+1], split[n+2] = unicode.sub(split[n], 1, pos-1), "\\N", unicode.sub split[n], pos+1 48 | curCnt -= unicode.len split[n+2] 49 | n += 1 50 | -- no whitespace character found -> force the line break at n chars 51 | else split[n+1] = "\\N" 52 | n += 1 53 | n += 1 54 | section.value = table.concat split 55 | 56 | data\callback textSectionCb, ASS.Section.Text 57 | data\commit! 58 | lines\replaceLines! 59 | 60 | showDialog = (sub, sel) -> 61 | dlg = { 62 | { 63 | class: "label", label: "Insert \\N after", 64 | x: 0, y: 0, width: 1, height: 1 65 | }, 66 | { 67 | class: "intedit", name: "charLimit", 68 | x: 1, y: 0, width: 1, height: 1, value: 35 69 | }, 70 | { 71 | class: "label", label: "characters", 72 | x: 2, y: 0, width: 1, height: 1 73 | }, 74 | } 75 | 76 | btn, res = aegisub.dialog.display dlg 77 | insertLineBreaks sub, sel, res if btn 78 | 79 | depCtrl\registerMacro showDialog 80 | -------------------------------------------------------------------------------- /l0.LerpByChar.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Lerp by Character" 2 | export script_description = "Linearly interpolates a specified override tag character-by-character between stops in a line." 3 | export script_version = "0.1.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.LerpByChar" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depCtrl = DependencyControl { 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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.4.4", 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 | {"a-mo.ConfigHandler", version: "1.1.4", url: "https://github.com/TypesettingTools/Aegisub-Motion", 18 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 19 | } 20 | } 21 | LineCollection, ASS, Functional, ConfigHandler = depCtrl\requireModules! 22 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 23 | logger = depCtrl\getLogger! 24 | 25 | dialogs = { 26 | lerpByCharacter: { 27 | { 28 | class: "label", label: "Select a tag to interpolate between states already present in the line.", 29 | x: 0, y: 0, width: 6, height: 1 30 | }, 31 | { 32 | class: "label", label: "Tag:", 33 | x: 0, y: 1, width: 1, height: 1 34 | }, 35 | tag: { 36 | class: "dropdown", 37 | items: table.pluck table.filter(ASS.tagMap, (tag) -> tag.type.lerp and not tag.props.global), "overrideName", 38 | value: "\\1c", config: true, 39 | x: 1, y: 1, width: 1, height: 1 40 | }, 41 | cleanTags: { 42 | class: "checkbox", label: "Omit redundant tags", 43 | value: true, config: true, 44 | x: 0, y: 2, width: 2, height: 1 45 | }, 46 | }, 47 | } 48 | 49 | groupSectionsByTagState = (lineContents, tagName) -> 50 | groups, group = {} 51 | cb = (section, sections, i) -> 52 | if i == 1 or section.instanceOf[ASS.Section.Tag] and 0 < #section\getTags tagName -- TODO: support master tags and resets 53 | tagState = (section\getEffectiveTags true).tags[tagName] 54 | group.endTagState = tagState if group 55 | group = sections: {}, startTagState: tagState, firstLineIndex: i 56 | groups[#groups + 1] = group 57 | elseif i == #sections 58 | group.endTagState = (section\getEffectiveTags true).tags[tagName] 59 | group.sections[#group.sections + 1] = section 60 | 61 | lineContents\callback cb 62 | return groups 63 | 64 | lerpGroup = (sections, startTagState, endTagState) -> 65 | totalCharCount = list.reduce sections, 0, (totalLength, section) -> 66 | totalLength + (section.instanceOf[ASS.Section.Text] and section.len or 0) 67 | return false if totalCharCount == 0 68 | 69 | processedCharCount = 0 70 | lerpedSections, l = {}, 1 71 | for s, section in ipairs sections 72 | unless section.instanceOf[ASS.Section.Text] 73 | lerpedSections[l], l = section, l+1 74 | continue 75 | 76 | charCount = section.len 77 | 78 | previousSection = sections[s-1] 79 | start = if previousSection and previousSection.instanceOf[ASS.Section.Tag] 80 | previousSection\removeTags startTagState.__tag.name 81 | previousSection\insertTags startTagState\lerp endTagState, processedCharCount / totalCharCount 82 | l, lerpedSections[l], section = l+1, section\splitAtChar 2, true 83 | 2 84 | else 1 85 | 86 | for i = start, charCount 87 | tag = startTagState\lerp endTagState, (processedCharCount + i-1) / totalCharCount 88 | lerpedSections[l] = ASS.Section.Tag {tag} 89 | l, lerpedSections[l+1], section = l+2, section\splitAtChar 2, true 90 | 91 | processedCharCount += charCount 92 | 93 | return lerpedSections 94 | 95 | showDialog = (macro) -> 96 | options = ConfigHandler dialogs, depCtrl.configFile, false, script_version, depCtrl.configDir 97 | options\read! 98 | options\updateInterface macro 99 | btn, res = aegisub.dialog.display dialogs[macro] 100 | if btn 101 | options\updateConfiguration res, macro 102 | options\write! 103 | return res 104 | 105 | lerpByCharacter = (sub, sel) -> 106 | res = showDialog "lerpByCharacter" 107 | return aegisub.cancel! unless res 108 | 109 | tagName = ASS.tagNames[res.tag][1] 110 | lines = LineCollection sub, sel 111 | 112 | for line in *lines 113 | ass = ASS\parse line 114 | groups = groupSectionsByTagState(ass, tagName) 115 | 116 | insertOffset = 0 117 | for group in *groups do with group 118 | lerpedSections = lerpGroup .sections, .startTagState, .endTagState 119 | continue unless lerpedSections 120 | ass\removeSections insertOffset + .firstLineIndex , insertOffset + .firstLineIndex + #.sections - 1 121 | ass\insertSections lerpedSections, insertOffset + .firstLineIndex 122 | insertOffset += #lerpedSections - #.sections 123 | 124 | ass\cleanTags 4 if res.cleanTags 125 | ass\commit! 126 | lines\replaceLines! 127 | 128 | depCtrl\registerMacro lerpByCharacter 129 | -------------------------------------------------------------------------------- /l0.MergeDrawings.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Merge Drawings" 2 | export script_description = [[Moves all drawings found in all selected lines into the first line. 3 | Maintains positioning and converts scale as well as alignment.]] 4 | export script_version = "0.2.0" 5 | export script_author = "line0" 6 | export script_namespace = "l0.MergeDrawings" 7 | 8 | DependencyControl = require "l0.DependencyControl" 9 | 10 | rec = DependencyControl{ 11 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/DependencyControl.json", 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 | } 18 | } 19 | 20 | LineCollection, ASS = rec\requireModules! 21 | logger = rec\getLogger! 22 | 23 | getScriptListDlg = (macros, modules) -> 24 | 25 | mergeDrawings = (sub, sel, res, lines, lineCnt, targetLine) -> 26 | target = {name, ASS\createTag(name, value) for name, value in pairs res} 27 | mergedLines, targetSection = {} 28 | targetScaleX, targetScaleY = target.scale_x.value/100, target.scale_y.value/100 29 | 30 | lineCb = (lines, line, i) -> 31 | aegisub.cancel! if aegisub.progress.is_cancelled! 32 | aegisub.progress.task "Processed %d of %d lines..."\format i, lineCnt if i%10==0 33 | 34 | data = i==1 and targetLine or ASS\parse line 35 | pos, align = data\getPosition! 36 | tags = (data\getEffectiveTags -1, true, true, false).tags 37 | local haveTextSection 38 | 39 | data\callback (section) -> 40 | if section.class == ASS.Section.Drawing 41 | -- determine target drawing section to merge drawings into 42 | targetSection = section if i==1 43 | -- get a copy of the position tag which needs to be 44 | -- applied as an offset to the drawing 45 | off = pos.class == ASS.Tag.Move and pos.startPos\copy! or pos\copy! 46 | 47 | -- determine the top/left bounds of the drawing in order to make 48 | -- the drawing start at the coordinate origin 49 | bounds = section\getBounds! 50 | -- trim drawing in order to scale shapes without causing them to move 51 | section\sub bounds[1] 52 | -- add the scaled bounds to our offset 53 | scaleX, scaleY = tags.scale_x.value/100, tags.scale_y.value/100 54 | off\add bounds[1]\mul scaleX, scaleY 55 | facX, facY = scaleX / targetScaleX, scaleY / targetScaleY 56 | unless facX == 1 and facY == 1 57 | section\mul facX, facY 58 | -- now apply the position offset scaled by the target fscx/fscy values 59 | section\add off\div targetScaleX, targetScaleY 60 | 61 | -- set intermediate point of origin alignment 62 | unless align\equal 7 63 | ex = section\getExtremePoints true 64 | srcOff = align\getPositionOffset ex.w, ex.h 65 | section\sub srcOff 66 | 67 | if i > 1 68 | -- insert contours into first line, create a drawing section if none exists 69 | targetSection or= (targetLine\insertSections ASS.Section.Drawing!)[1] 70 | targetSection\insertContours section 71 | return false 72 | 73 | elseif section.class == ASS.Section.Text 74 | haveTextSection or= true 75 | 76 | if i > 1 77 | -- remove drawings from original lines and mark empty lines for deletion 78 | if haveTextSection then data\commit! 79 | else mergedLines[#mergedLines+1] = line 80 | 81 | aegisub.progress.set 100*i/lineCnt 82 | 83 | -- process all selected lines 84 | lines\runCallback lineCb, true 85 | 86 | -- update tags and aligment 87 | targetLine\replaceTags [tag for _,tag in pairs target] 88 | unless target.align\equal 7 89 | ex = targetSection\getExtremePoints true 90 | off = target.align\getPositionOffset ex.w, ex.h 91 | targetSection\add off 92 | 93 | pos, align = targetLine\getPosition! 94 | bounds = targetSection\getBounds! 95 | targetSection\sub bounds[1] 96 | if pos.class == ASS.Tag.Move 97 | pos.endPos\sub pos.startPos 98 | pos.endPos\add bounds[1] 99 | pos.startPos\set bounds[1].x, bounds[1].y 100 | else 101 | targetLine\replaceTags{ASS\createTag "position", bounds[1]} 102 | 103 | targetLine\commit! 104 | lines\replaceLines! 105 | lines\deleteLines mergedLines 106 | 107 | showDialog = (sub, sel) -> 108 | lines = LineCollection sub, sel 109 | lineCnt = #lines.lines 110 | return if lineCnt == 0 111 | 112 | data = ASS\parse lines.lines[lineCnt] -- first line 113 | tags = (data\getEffectiveTags -1, true, true, false).tags 114 | scale_x, scale_y, align = tags.scale_x\get!, tags.scale_y\get!, tags.align\get! 115 | 116 | btn, res = aegisub.dialog.display { 117 | { 118 | class: "label", label: "Target Alignment: ", 119 | x: 0, y: 0, width: 1, height: 1 120 | }, 121 | { 122 | class: "dropdown", name: "align", 123 | items: [i for i=1,9], value: align, 124 | x: 1, y: 0, width: 1, height: 1, 125 | }, 126 | { 127 | class: "label", label: "Target Scale X: ", 128 | x: 0, y: 1, width: 1, height: 1 129 | }, 130 | { 131 | class: "floatedit", name: "scale_x", 132 | min: 0.01, value: scale_x, 133 | x: 1, y: 1, width: 1, height: 1 134 | }, 135 | { 136 | class: "label", label: "Target Scale Y: ", 137 | x: 0, y: 2, width: 1, height: 1 138 | }, 139 | { 140 | class: "floatedit", name: "scale_y", 141 | min: 0.01, value: scale_y, 142 | x: 1, y: 2, width: 1, height: 1 143 | } 144 | } 145 | 146 | if btn 147 | mergeDrawings sub, sel, res, lines, lineCnt, data 148 | 149 | rec\registerMacro showDialog 150 | -------------------------------------------------------------------------------- /l0.MoveAlongPath.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Move Along Path" 2 | export script_description = "Moves text along a path specified in a \\clip. Currently only works on fbf lines." 3 | export script_version = "0.2.1" 4 | export script_author = "line0" 5 | export script_namespace = "l0.MoveAlongPath" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | version = DependencyControl { 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/DependencyControl.json", 10 | { 11 | "aegisub.util", 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 | {"a-mo.Line", version: "1.5.3", url: "https://github.com/TypesettingTools/Aegisub-Motion", 15 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 16 | {"l0.ASSFoundation", version: "0.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 17 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 18 | {"l0.Functional", version: "0.5.0", url: "https://github.com/TypesettingTools/Functional", 19 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 20 | "Yutils" 21 | } 22 | } 23 | util, LineCollection, Line, ASS, Functional, Yutils = version\requireModules! 24 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 25 | logger = version\getLogger! 26 | 27 | getLengthWithinBox = (w, h, angle) -> -- currently unused because only horizontal metrics are being used 28 | return 0 if w == 0 or h == 0 29 | angle %= 180 30 | return w if angle == 0 31 | return h if angle == 90 32 | 33 | angle = math.rad angle > 90 and 180-angle or angle 34 | A = math.atan2 h, w 35 | a, b = w, h 36 | 37 | if angle < A 38 | b = w * math.tan angle 39 | elseif angle > A 40 | a = h 41 | b = h / math.tan angle 42 | 43 | return Yutils.math.distance a,b 44 | 45 | process = (sub,sel,res) -> 46 | aegisub.progress.task("Processing...") 47 | 48 | lines = LineCollection sub,sel 49 | id = util.uuid! 50 | 51 | -- get total duration of the fbf lines 52 | totalDuration = -lines.lines[1].duration 53 | lines\runCallback (lines, line) -> 54 | totalDuration += line.duration 55 | 56 | startDist, metricsCache, path, posOff, angleOff, totalLength = 0, {} 57 | linesToDelete, lineCnt, finalLineCnt, firstLineNum = {}, #lines.lines, 0 58 | alignOffset = { 59 | (w, a) -> w * math.cos math.rad a, -- right 60 | -> 0, -- left 61 | (w, a) -> w/2 * math.cos math.rad a, -- center 62 | } 63 | 64 | lineCb = (lines, line, i) -> 65 | aegisub.cancel! if aegisub.progress.is_cancelled! 66 | 67 | linesToDelete[i], orgText = line, line.text 68 | ass = ASS\parse line 69 | if i == 1 -- get path ass and relative position/angle from first line 70 | path = ass\removeTags({"clip_vect","iclip_vect"})[1] 71 | logger\assert path, "Error: couldn't find \\clip containing path in first line, aborting." 72 | angleOff = path\getAngleAtLength 0 73 | posOff = path.contours[1].commands[1]\get! 74 | totalLength = path\getLength! 75 | firstLineNum = line.number 76 | 77 | ass\reverse! if res.reverseLine 78 | 79 | -- split line by characters 80 | charOff, charLines = 0, ass\splitAtIntervals 1, 4, false 81 | for j = 1, #charLines 82 | charAss, length = charLines[j].ASS, startDist + charOff 83 | -- get font metrics 84 | w = charAss\getTextExtents! 85 | -- calculate new position and angle 86 | targetPos = path\getPositionAtLength length, true 87 | angle = path\getAngleAtLength(length + w/2, true) or path\getAngleAtLength length, true 88 | -- stop processing this frame if he have reached the end of the path 89 | break unless targetPos 90 | -- get tags effective as of the first section (we know there won't be any tags after that) 91 | effTags = charAss.sections[1]\getEffectiveTags(true, true, false).tags 92 | 93 | -- calculate final rotation and write tags 94 | if res.aniFrz 95 | angle\add 180 if res.flipFrz 96 | charAss\replaceTags angle 97 | 98 | -- calculate how much "space" the character takes up on the line 99 | -- and determine the distance offset for the next character 100 | -- this currently only uses horizontal metrics so it breaks if you disable rotation animation 101 | charOff += w 102 | 103 | if res.aniPos 104 | an = effTags.align\get! 105 | targetPos\add alignOffset[an%3 + 1](w, angle.value), alignOffset[an%3 + 1](w, angle.value+90) 106 | 107 | if res.relPos 108 | targetPos\sub posOff 109 | targetPos\add effTags.position 110 | 111 | charAss\replaceTags targetPos 112 | 113 | charAss\commit! 114 | 115 | if charAss\getLineBounds(true).w != 0 116 | charLines[j]\setExtraData version.namespace, {settings: res, :id, orgLine: j==1 and orgText or nil} 117 | lines\addLine charLines[j], nil, true, firstLineNum + finalLineCnt 118 | finalLineCnt += 1 119 | 120 | framePct = res.cfrMode and 1 or lineCnt * line.duration / totalDuration 121 | time = (i^res.accel) / (lineCnt^res.accel) 122 | startDist = util.interpolate time*framePct, 0, totalLength 123 | aegisub.progress.set i * 100 / lineCnt 124 | 125 | lines\runCallback lineCb, true 126 | lines\deleteLines linesToDelete 127 | lines\insertLines! 128 | 129 | hasClip = (sub, sel, active) -> 130 | return false if #sel == 0 131 | firstLine = Line sub[sel[1]] 132 | ass = ASS\parse firstLine 133 | if 0 == #ass\getTags {"clip_vect","iclip_vect"} 134 | return false, "No \\clip or \\iclip containing the path found in first line of the selection." 135 | 136 | return true 137 | 138 | getExtraData = (line) -> 139 | if line.extra and line.extra[script_namespace] 140 | extra = json.decode line.extra[script_namespace] 141 | return extra if extra.id 142 | 143 | hasUndoData = (sub, sel, active) -> 144 | for i = 1, #sel 145 | return true if sel[i] and getExtraData sub[sel[i]] 146 | return false 147 | 148 | undo = (sub, sel) -> 149 | ids, toDelete, j = {}, {}, 1 150 | for i = 1, #sel 151 | extra = getExtraData sub[sel[i]] 152 | if extra 153 | ids[extra.id] = true 154 | 155 | sel = {} 156 | for i, line in ipairs sub 157 | extra = getExtraData line 158 | if extra and ids[extra.id] 159 | if extra.orgLine 160 | sel[j], j = i, j+1 161 | else toDelete[#toDelete+1] = i 162 | 163 | lines = LineCollection sub,sel 164 | lines\runCallback (lines, line) -> 165 | line.text = line\getExtraData(script_namespace).orgLine 166 | line.extra[script_namespace] = nil 167 | 168 | lines\replaceLines! 169 | sub.delete toDelete 170 | 171 | showDialog = (sub, sel) -> 172 | dlg = { 173 | { 174 | class: "label", label: "Select which tags are to be animated along the path specified as a \\clip:", 175 | x: 0, y: 0, width: 8, height: 1, 176 | }, 177 | { 178 | class: "checkbox", name: "aniPos", label: "Animate Position:", 179 | x: 0, y: 1, width: 4, height: 1, value: true 180 | }, 181 | { 182 | class: "label", label: "Acceleration:", 183 | x: 4, y: 1, width: 3, height: 1, 184 | }, 185 | { 186 | class: "floatedit", name: "accel", 187 | x: 7, y: 1, width: 1, height: 1, value: 1.0, step: 0.1 188 | }, 189 | { 190 | class: "checkbox", name: "relPos", label: "Offset existing position", 191 | x: 4, y: 2, width: 4, height: 1, value: false 192 | }, 193 | { 194 | class: "checkbox", name: "cfrMode", label: "CFR mode (ignores frame timings)", 195 | x: 4, y: 3, width: 4, height: 1, value: true 196 | }, 197 | { 198 | class: "checkbox", 199 | name: "aniFrz", label: "Animate Rotation", 200 | x: 0, y: 5, width: 4, height: 1, value: true 201 | }, 202 | { 203 | class: "checkbox", 204 | name: "flipFrz", label: "Rotate final lines by 180°", 205 | x: 4, y: 5, width: 4, height: 1, value: false 206 | }, 207 | { 208 | class: "label", label: "Options:", 209 | x: 0, y: 7, width: 4, height: 1, value: false 210 | }, 211 | { 212 | class: "checkbox", name: "reverseLine", label: "Reverse Line Contents", 213 | x: 4, y: 7, width: 4, height: 1, value: false 214 | } 215 | } 216 | 217 | btn, res = aegisub.dialog.display dlg 218 | process sub,sel,res if btn 219 | 220 | version\registerMacros { 221 | {script_name, nil, showDialog, hasClip}, 222 | {"Undo", nil, undo, hasUndoData} 223 | } 224 | -------------------------------------------------------------------------------- /l0.Nudge.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Nudge" 2 | export script_description = "Provides configurable and hotkeyable tag/line modification macros." 3 | export script_version = "0.5.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.Nudge" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depCtrl = DependencyControl { 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/DependencyControl.json", 10 | { 11 | "aegisub.clipboard", "json", 12 | {"a-mo.LineCollection", version: "1.3.0", url: "https://github.com/TypesettingTools/Aegisub-Motion"}, 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 | {"l0.Functional", version: "0.5.0", url: "https://github.com/TypesettingTools/Functional", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 17 | } 18 | } 19 | 20 | clipboard, json, LineCollection, ASS, Functional = depCtrl\requireModules! 21 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 22 | logger = depCtrl\getLogger! 23 | 24 | -------- Nudger Class ------------------- 25 | 26 | cmnOps = {"Add", "Multiply", "Power", "Cycle", "Set", "Set Default", "Remove", "Copy", "Paste Over", "Paste Into"} 27 | colorOps = list.join cmnOps, {"Add HSV"} 28 | stringOps = {"Append", "Prepend", "Replace", "Cycle", "Set", "Set Default", "Remove"} 29 | drawingOps = {"Add", "Multiply", "Power", "Remove", "Copy", "Paste Over", "Paste Into", "Expand", "Convert To Clip"} 30 | clipOpsVect = list.join drawingOps, {"Invert Clip", "Convert To Drawing", "Set Default"} 31 | clipOptsRect = list.join cmnOps, {"Invert Clip", "Convert To Drawing"} 32 | 33 | msgs = { 34 | configuation: { 35 | load: { 36 | unsupportedConfigFileVersion: "Your configuration file version (%s) is incompatible with %s %s. 37 | Please delete %s and reload your scripts." 38 | } 39 | } 40 | } 41 | 42 | class Nudger 43 | @operations = list.makeSet { 44 | "Align Up", "Align Down", "Align Left", "Align Right", "Set Default", "Cycle", "Remove", "Convert To Drawing", 45 | "Set Comment", "Unset Comment", "Toggle Comment", "Copy", "Paste Over", "Paste Into", "Expand", "Convert To Clip" 46 | }, { 47 | Add: "add", Multiply: "mul", Power: "pow", Set: "set", Toggle: "toggle", Replace: "replace" 48 | Append: "append", Prepend: "prepend", ["Auto Cycle"]: "cycle", ["Add HSV"]: "addHSV", 49 | ["Invert Clip"]: "toggleInverse" 50 | }, nil, false 51 | 52 | @targets = { 53 | tags: { 54 | position: cmnOps, 55 | blur_edges: cmnOps, 56 | scale_x: cmnOps, scale_y: cmnOps, 57 | align: {"Align Up", "Align Down", "Align Left", "Align Right", "Auto Cycle", "Set", "Set Default", "Cycle"}, 58 | angle: cmnOps, angle_y: cmnOps, angle_x: cmnOps, 59 | outline: cmnOps, outline_x: cmnOps, outline_y: cmnOps, 60 | shadow: cmnOps, shadow_x: cmnOps, shadow_y: cmnOps, 61 | alpha: cmnOps, alpha1: cmnOps, alpha2: cmnOps, alpha3: cmnOps, alpha4: cmnOps, ["Alphas"]: cmnOps, 62 | color1: colorOps, color2: colorOps, color3: colorOps, color4: colorOps, 63 | ["Colors"]: colorOps, ["Primary Color"]: colorOps 64 | blur: cmnOps, 65 | shear_x: cmnOps, shear_y: cmnOps, 66 | bold: list.join(cmnOps,{"Toggle"}), 67 | underline: {"Toggle","Set", "Set Default"}, 68 | spacing: cmnOps, 69 | fontsize: cmnOps, 70 | k_fill: cmnOps, k_sweep_alt: cmnOps, k_sweep: cmnOps, k_bord: cmnOps, 71 | move: cmnOps, move_simple: cmnOps, 72 | origin: cmnOps, 73 | wrapstyle: {"Auto Cycle","Cycle", "Set", "Set Default"}, 74 | fade_simple: cmnOps, fade: cmnOps, ["Fades"]: cmnOps, 75 | italic: {"Toggle","Set", "Set Default"}, 76 | reset: stringOps, 77 | fontname: stringOps, 78 | clip_vect: clipOpsVect, iclip_vect: clipOpsVect, clip_rect: clipOptsRect, iclip_rect: clipOptsRect, 79 | ["Clips (Vect)"]: clipOpsVect, ["Clips (Rect)"]: clipOptsRect, Clips: clipOpsVect, 80 | unknown: {"Remove"}, junk: {"Remove"}, Comment: {"Remove"}, ["Comments/Junk"]: {"Remove"} 81 | ["Any Tag"]: {"Remove", "Copy", "Paste Over", "Paste Into"}, 82 | }, 83 | line: { 84 | Line: {"Set Comment", "Unset Comment", "Toggle Comment"}, 85 | Text: {"Convert To Drawing", "Expand", "Convert To Clip"}, 86 | Drawing: drawingOps, 87 | Contents: {"Convert To Drawing", "Expand"} 88 | } 89 | } 90 | 91 | @compoundTargets = { 92 | Colors: {"color1","color2","color3","color4"}, 93 | Alphas: {"alpha", "alpha1", "alpha2", "alpha3", "alpha4"}, 94 | Fades: {"fade_simple", "fade"}, 95 | Clips: {"clip_vect", "clip_rect", "iclip_vect", "iclip_rect"}, 96 | ["Clips (Vect)"]: {"clip_vect", "iclip_vect"}, 97 | ["Clips (Rect)"]: {"clip_rect", "iclip_rect"}, 98 | ["\\move"]: {"move", "move_simple"}, 99 | ["Any Tag"]: ASS.tagNames.all, 100 | Contents: {"Text", "Drawing"} 101 | } 102 | 103 | @targetList = list.join table.keys(@@targets.line), 104 | [ASS.toFriendlyName[name] or name for name, _ in pairs @@targets.tags] 105 | 106 | new: (params = {}) => 107 | @name = params.name or "Unnamed Nudger" 108 | @tag = params.tag or "position" 109 | @operation = params.operation or "Add" 110 | @value = params.value or {} 111 | @id = params.id or util.uuid! 112 | @noDefault = params.noDefault or false 113 | @keepEmptySections = params.keepEmptySections == nil and true or params.keepEmptySections 114 | @targetValue = params.targetValue or 0 115 | @targetName = params.targetName or "Tag Section" 116 | @validate! 117 | 118 | validate: => 119 | -- do we need to check the other values? 120 | ops = @@targets.tags[@tag] or @@targets.line[@tag] 121 | logger\assert list.indexOf(ops, @operation), 122 | "Operation %s not supported for tag or section %s.", @operation, @tag 123 | 124 | nudgeTags: (lineData, lines, line, targets) => 125 | tagSect = @targetValue != 0 and tonumber(@targetValue) or nil 126 | relative = @targetName == "Matched Tag" 127 | builtinOp = @@operations[@operation] 128 | 129 | foundTags = lineData\getTags targets, tagSect, tagSect, relative 130 | foundCnt = #foundTags 131 | 132 | -- insert default tags if no matching tags are present 133 | if foundCnt == 0 and not @noDefault and not relative and @operation != "Remove" 134 | lineData\insertDefaultTags targets, tagSect 135 | 136 | if builtinOp 137 | lineData\modTags targets, 138 | (tag) -> tag[builtinOp] tag, unpack @value, 139 | tagSect, tagSect, relative 140 | return 141 | 142 | switch @operation 143 | when "Copy" 144 | tagStr = {} 145 | lineData\modTags targets, 146 | (tag) -> tagStr[#tagStr+1] = tag\getTagString!, 147 | tagSect, tagSect, relative 148 | clipboard.set table.concat tagStr 149 | 150 | when "Paste Over" 151 | pasteTags = ASS.TagList(ASS.Section.Tag(clipboard.get!))\filterTags targets 152 | lineData\replaceTags pasteTags, tagSect, tagSect, relative 153 | 154 | when "Paste Into" 155 | pasteTags = ASS.TagList ASS.Section.Tag clipboard.get! 156 | global, normal = pasteTags\filterTags targets, global: true 157 | lineData\insertTags normal, tagSect, -1, not relative 158 | lineData\replaceTags global 159 | 160 | when "Cycle" 161 | edField = "l0.Nudge.cycleState" 162 | ed = line\getExtraData edField 163 | if type(ed) == "table" 164 | ed[@id] = ed[@id] and ed[@id] < #@value and ed[@id] + 1 or 1 165 | else ed = {[@id]: 1} 166 | line\setExtraData edField, ed 167 | 168 | lineData\modTags targets, 169 | (tag) -> tag\set unpack @value[ed[@id]], 170 | tagSect, tagSect, relative 171 | 172 | when foundCnt > 0 and "Set Default" 173 | defaults = lineData\getStyleDefaultTags! 174 | lineData\modTags targets, (tag) -> 175 | tag\set defaults.tags[tag.__tag.name]\get!, 176 | tagSect, tagSect, relative 177 | 178 | lineData\cleanTags 1, false 179 | 180 | when "Expand" 181 | lineData\modTags targets, 182 | (tag) -> tag\expand @value[1], @value[2], 183 | tagSect, tagSect, relative 184 | 185 | when "Convert To Drawing" 186 | keepPos, drawing, pos = not @value[2] 187 | lineData\modTags targets, (tag) -> 188 | drawing, pos = tag\getDrawing(keepPos) 189 | return @value[1] == true, 190 | tagSect, tagSect, relative 191 | 192 | lineData\insertSections drawing 193 | lineData\replaceTags pos if pos 194 | 195 | when "Remove" 196 | lineData\removeTags targets, tagSect, tagSect, relative 197 | 198 | else 199 | opAlign = re.match @operation, "Align (Up|Down|Left|Right)" 200 | if opAlign 201 | pos, align, org = lineData\getPosition! 202 | newAlign = align\copy! 203 | newAlign[string.lower(opAlign[2].str)] newAlign 204 | 205 | if @value[1] == true 206 | haveDrawings, haveRotation, w, h = false, false 207 | lineData\callback (section,sections,i) -> haveDrawings = true, 208 | ASS.Section.Drawing 209 | 210 | -- While text uses type metrics for positioning and alignment 211 | -- vector drawings use a straight bounding box 212 | -- TODO: make this work for lines that have both drawings AND text 213 | if haveDrawings 214 | bounds = lineData\getLineBounds! 215 | w, h = bounds.w, bounds.h 216 | else 217 | metrics = lineData\getTextMetrics true 218 | w, h = metrics.width, metrics.height 219 | 220 | pos\add newAlign\getPositionOffset w, h, align 221 | 222 | -- add origin if any rotation is applied to the line 223 | effTags = lineData\getEffectiveTags -1, true, true, false 224 | trans, tags = effTags\checkTransformed!, effTags.tags 225 | if tags.angle\modEq(0, 360) and tags.angle_x\modEq(0, 360) and tags.angle_y\modEq(0, 360) and not (trans.angle or trans.angle_x or trans.angle_y) 226 | lineData\replaceTags {newAlign, pos} 227 | else lineData\replaceTags {newAlign, org, pos} 228 | 229 | else lineData\replaceTags {newAlign} 230 | 231 | nudgeLines: (lineData, lines, line, targets) => 232 | op = @operation 233 | relative = @targetName == "Matched Tag" 234 | tagSect = @targetValue != 0 and @targetValue or nil 235 | 236 | if targets["Line"] 237 | line.comment = switch op 238 | when "Unset Comment" then false 239 | when "Set Comment" then true 240 | when "Toggle Comment" then not line.comment 241 | 242 | if targets["Text"] 243 | if op == "Convert To Clip" 244 | local toConvert 245 | lineData\callback (sect) -> 246 | toConvert = sect\convertToDrawing! 247 | return false, 248 | ASS.Section.Text, 1, 1, true 249 | if toConvert 250 | lineData\replaceTags toConvert\getClip! 251 | else 252 | lineData\callback (sect) -> 253 | switch op 254 | when "Convert To Drawing" then sect\convertToDrawing! 255 | when "Expand" then sect\expand(@value[1], @value[2]), 256 | ASS.Section.Text, tagSect, tagSect, relative 257 | 258 | if targets["Drawing"] or targets["Text"] 259 | targetSections = {targets["Drawing"] and ASS.Section.Drawing, targets["Text"] and ASS.Section.Text} 260 | switch op 261 | when "Copy" 262 | sectStr = {} 263 | lineData\callback (sect) -> sectStr[#sectStr+1] = sect\getString!, 264 | targetSections, tagSect, tagSect, relative 265 | clipboard.set table.concat sect 266 | when "Paste Over" 267 | sectStr = clipboard.get! 268 | lineData\callback (sect) -> 269 | if sect.class == ASS.Section.Text 270 | sect.value = sectStr 271 | else return ASS.Section.Drawing str: sectStr, 272 | targetSections, tagSect, tagSect, relative 273 | when "Paste Into" 274 | sectStr = clipboard\get! 275 | if targets["Drawing"] and sectStr:match("m%s+[%-%d%.]+%s+[%-%d%.]+") 276 | lineData\insertSections ASS.Section.Drawing str: sectStr 277 | elseif targets["Text"] 278 | lineData\insertSections ASS.Section.Text sectStr 279 | when "Convert To Clip" 280 | local clip 281 | lineData\callback (sect) -> 282 | if clip 283 | clip\insertContours sect\getClip! 284 | else 285 | clip = sect\getClip! 286 | return false, 287 | ASS.Section.Drawing, tagSect, tagSect, relative 288 | if clip then 289 | lineData\replaceTags clip 290 | 291 | if targets["Drawing"] 292 | builtinOp = @@operations[@operation] 293 | lineData\callback (sect) -> 294 | if builtinOp 295 | sect[builtinOp] sect, unpack @value 296 | elseif op == "Expand" 297 | sect\expand @value[1], @value[2], 298 | ASS.Section.Drawing, tagSect, tagSect, relative 299 | 300 | if targets["Comments/Junk"] and op == "Remove" 301 | lineData\stripComments! 302 | lineData\removeTags "junk", tagSect, tagSect, relative 303 | 304 | elseif targets["Comment"] and op == "Remove" 305 | lineData\stripComments! 306 | 307 | nudge: (sub, sel) => 308 | targets, tagTargets, lineTargets = @@compoundTargets[@tag], {}, {} 309 | if targets 310 | for i = 1, #targets 311 | if ASS.tagMap[targets[i]] 312 | tagTargets[#tagTargets+1] = targets[i] 313 | else 314 | lineTargets[#lineTargets+1] = targets[i] 315 | lineTargets[targets[i]] = true 316 | elseif ASS.tagMap[@tag] 317 | tagTargets[1] = @tag 318 | else 319 | lineTargets[1], lineTargets[@tag] = @tag, true 320 | 321 | lines = LineCollection sub, sel, () -> true 322 | lines\runCallback (lines, line) -> 323 | lineData = ASS\parse line 324 | if #tagTargets > 0 325 | @nudgeTags lineData, lines, line, tagTargets 326 | if #lineTargets > 0 327 | @nudgeLines lineData, lines, line, lineTargets 328 | 329 | lineData\commit nil, @keepEmptySections 330 | lines\replaceLines! 331 | table.sort Nudger.targetList 332 | 333 | 334 | encodeDlgResName = (id, name) -> "#{id}.#{name}" 335 | decodeDlgResName = (un) -> un\match "([^%.]+)%.(.+)" 336 | 337 | class Configuration 338 | @default = { 339 | __version: script_version, 340 | nudgers: { 341 | {operation: "Add", value: {1,0}, id: "d0dad24e-515e-40ab-a120-7b8d24ecbad0", name: "Position Right (+1)", tag: "position"}, 342 | {operation: "Add", value: {-1,0}, id: "0c6ff644-ef9c-405a-bb12-032694d432c0", name: "Position Left (-1)", tag: "position"}, 343 | {operation: "Add", value: {0,-1}, id: "cb2ec6c1-a8c1-48b8-8a13-cafadf55ffdd", name: "Position Up (-1)", tag: "position"}, 344 | {operation: "Add", value: {0,1}, id: "cb9c1a5b-6910-4fb2-b457-a9c72a392d90", name: "Position Down (+1)", tag: "position"}, 345 | {operation: "Cycle", value: {{0.6},{0.8},{1},{1.2},{1.5},{2},{3},{4},{5},{8}}, id: "c900ef51-88dd-413d-8380-cebb7a59c793", name: "Cycle Blur", tag: "blur"}, 346 | {operation: "Cycle", value: {{255},{0},{16},{48},{96},{128},{160},{192},{224}}, id: "d338cbca-1575-4795-9b80-3680130cce62", name: "Cycle Alpha", tag: "alpha"}, 347 | {operation: "Toggle", value: {}, id: "974c3af9-ef51-45f5-a992-4850cb006743", name: "Toggle Bold", tag: "bold"}, 348 | {operation: "Auto Cycle", value: {}, id: "aa74461a-477b-47de-bbf4-16ef1ee568f5", name: "Cycle Wrap Styles", tag: "wrapstyle"}, 349 | {operation: "Align Up", value: {true}, id: "254bf380-22bc-457b-abb7-3d1f85b90eef", name: "Align Up", tag: "align"}, 350 | {operation: "Align Down", value: {true}, id: "260318dc-5bdd-4975-9feb-8c95b41e7b5b", name: "Align Down", tag: "align"}, 351 | {operation: "Align Left", value: {true}, id: "e6aeca35-d4e0-4ff4-81ac-8d3a853d5a9c", name: "Align Left", tag: "align"}, 352 | {operation: "Align Right", value: {true}, id: "dd80e1c5-7c07-478c-bc90-7c473c3abe49", name: "Align Right", tag: "align"}, 353 | {operation: "Set", value: {1}, id: "18a27245-5306-4990-865c-ae7f0062083a", name: "Add Edgeblur", tag: "blur_edges"}, 354 | {operation: "Set Default", value: {1}, id: "bb4967a7-fb8a-4907-b5e8-395ea67c0a52", name: "Default Origin", tag: "origin"}, 355 | {operation: "Add HSV", value: {0,0,0.1}, id: "015cd09b-3c2b-458e-a65a-80b80bb951b1", name: "Brightness Up", tag: "Colors"}, 356 | {operation: "Add HSV", value: {0,0,-0.1}, id: "93f07885-c3f7-41bb-b319-0542e6fd52d7", name: "Brightness Down", tag: "Colors"}, 357 | {operation: "Invert Clip", value: {}, id: "e719120a-e45a-44d4-b76a-62943f47d2c5", name: "Invert First Clip", tag: "Clips", 358 | noDefault: true, targetName: "Matched Tag", targetValue: "1"}, 359 | {operation: "Remove", value: {}, id: "4dfc33fd-3090-498b-8922-7e1eb4515257", name: "Remove Comments & Junk", tag: "Comments/Junk", noDefault: true}, 360 | {operation: "Remove", value: {}, id: "bc642b90-8ebf-45e8-a160-98b4658721bd", name: "Strip Tags", tag: "Any Tag", noDefault: true, keepEmptySections: false}, 361 | {operation: "Convert To Drawing", value: {false, false}, id: "9cf44e64-9ce9-402e-8097-9e189014c9c1", name: "Clips -> Drawing", tag: "Clips", noDefault: true}, 362 | } 363 | } 364 | 365 | new: (fileName) => 366 | @fileName = aegisub.decode_path(fileName) 367 | @nudgers = {} 368 | @load! 369 | 370 | load: => 371 | fileHandle = io.open @fileName 372 | local data 373 | if fileHandle 374 | data = json.decode fileHandle\read '*a' 375 | fileHandle\close! 376 | else 377 | data = @@default 378 | 379 | -- version checking 380 | logger\assert tonumber(data.__version\sub(3,3)) >= 3, 381 | msgs.configuation.load.unsupportedConfigFileVersion, data.__version, script_name, script_version, @fileName 382 | 383 | @nudgers = [Nudger nudgerConfig for nudgerConfig in *data.nudgers] 384 | @save! unless fileHandle 385 | 386 | save: => 387 | data = json.encode {nudgers: @nudgers, __version: script_version} 388 | fileHandle = io.open @fileName, 'w' 389 | fileHandle\write data 390 | fileHandle\close! 391 | 392 | addNudger: (params) => 393 | @nudgers[#@nudgers+1] = Nudger params 394 | 395 | removeNudger: (id) => 396 | @nudgers = list.filter @nudgers, (nudger) -> nudger.id != id 397 | 398 | getNudger: (id) => list.find @nudgers, (nudger) -> nudger.id == id 399 | 400 | getDialog: => 401 | dialog = { 402 | {class: "label", label: "Macro Name", x: 0, y: 0, width: 1, height: 1}, 403 | {class: "label", label: "Override Tag", x: 1, y: 0, width: 1, height: 1}, 404 | {class: "label", label: "Action", x: 2, y: 0, width: 1, height: 1}, 405 | {class: "label", label: "Value", x: 3, y: 0, width: 1, height: 1}, 406 | {class: "label", label: "Target", x: 4, y: 0, width: 1, height: 1}, 407 | {class: "label", label: "Target #", x: 5, y: 0, width: 1, height: 1}, 408 | {class: "label", label: "No Default", x: 6, y: 0, width: 1, height: 1}, 409 | {class: "label", label: "Keep Empty", x: 7, y: 0, width: 1, height: 1}, 410 | {class: "label", label: "Remove", x: 8, y: 0, width: 1, height: 1}, 411 | } 412 | 413 | getUnwrappedJson = (arr) -> 414 | jsonString = json.encode arr 415 | return jsonString\sub 2, jsonString\len!-1 416 | 417 | tags, operations = Nudger.targetList, table.keys Nudger.operations 418 | table.sort operations 419 | 420 | for i, nu in ipairs @nudgers 421 | dialog = list.join dialog, { 422 | {class: "edit", name: encodeDlgResName(nu.id, "name"), value: nu.name, x: 0, y: i, width: 1, height: 1}, 423 | {class: "dropdown", name: encodeDlgResName(nu.id, "tag"), items: tags, value: ASS.toFriendlyName[nu.tag] or nu.tag, 424 | x: 1, y: i, width: 1, height: 1}, 425 | {class: "dropdown", name: encodeDlgResName(nu.id, "operation"), items: operations, value: nu.operation, x: 2, y: i, width: 1, height: 1}, 426 | {class: "edit", name: encodeDlgResName(nu.id, "value"), value: getUnwrappedJson(nu.value), step: 0.5, x: 3, y: i, width: 1, height: 1}, 427 | {class: "dropdown", name: encodeDlgResName(nu.id, "targetName"), items: {"Tag Section", "Matched Tag"}, value: nu.targetName, x: 4, y: i, width: 1, height: 1}, 428 | {class: "intedit", name: encodeDlgResName(nu.id, "targetValue"), value: nu.targetValue, x: 5, y: i, width: 1, height: 1}, 429 | {class: "checkbox", name: encodeDlgResName(nu.id, "noDefault"), value: nu.noDefault, x: 6, y: i, width: 1, height: 1}, 430 | {class: "checkbox", name: encodeDlgResName(nu.id, "keepEmptySections"), value: nu.keepEmptySections, x: 7, y: i, width: 1, height: 1}, 431 | {class: "checkbox", name: encodeDlgResName(nu.id, "remove"), value: false, x: 8, y: i, width: 1, height: 1} 432 | } 433 | 434 | return dialog 435 | 436 | update: (res) => 437 | for k, v in pairs res 438 | id, name = decodeDlgResName k 439 | v = switch name 440 | when "value" then json.decode "[#{v}]" 441 | when "tag" then ASS.toTagName[v] or v 442 | else v 443 | 444 | if name == "remove" and v == true 445 | @removeNudger id 446 | elseif nudger = @getNudger(id) 447 | nudger[name] = v 448 | 449 | nudger\validate! for nudger in *@nudgers 450 | @registerMacros! 451 | 452 | registerMacros: => 453 | for nudger in *@nudgers 454 | aegisub.register_macro "#{script_name}/#{nudger.name}", script_description, 455 | (sub, sel) -> nudger\nudge sub, sel 456 | 457 | run: (noReload) => 458 | @load! unless noReload 459 | btn, res = aegisub.dialog.display @getDialog!, 460 | {"Save", "Cancel", "Add Nudger"}, 461 | {save: "Save", cancel: "Cancel", close: "Save"} 462 | 463 | switch btn 464 | when "Add Nudger" 465 | @addNudger! 466 | @run true 467 | when "Save" 468 | @update res 469 | @save! 470 | else @load! 471 | 472 | config = Configuration depCtrl\getConfigFileName! 473 | aegisub.register_macro "#{script_name}/Configure Nudge", script_description, () -> config\run! 474 | config\registerMacros! 475 | -------------------------------------------------------------------------------- /l0.PasteAiLines.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Paste AI Lines" 2 | export script_description = "Convenience macro for pasting full lines exported by AI2ASS." 3 | export script_version = "0.2.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.PasteAILines" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depCtrl = DependencyControl { 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/DependencyControl.json", 10 | { 11 | "aegisub.clipboard", 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 | {"a-mo.ConfigHandler", version: "1.1.4", url: "https://github.com/TypesettingTools/Aegisub-Motion", 15 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 16 | {"l0.ASSFoundation", version: "0.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 17 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 18 | {"l0.Functional", version: "0.5.0", url: "https://github.com/TypesettingTools/Functional", 19 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 20 | } 21 | } 22 | 23 | clipboard, LineCollection, ConfigHandler, ASS, Functional = depCtrl\requireModules! 24 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 25 | logger = depCtrl\getLogger! 26 | 27 | dlg = { 28 | main: { 29 | aiLinesRaw: { 30 | class: "textbox", 31 | text: aiLinesRaw, 32 | x: 0, y: 0, width: 4, height: 5 33 | }, 34 | trimDrawing: { 35 | class: "checkbox", label: "Trim drawings", hint: "Makes drawings start at the top left point of their bounding box instead of at 0,0.", 36 | value: true, config: true, x: 0, y: 5, width: 2, height: 1, 37 | }, 38 | trimAlignLabel: { 39 | class: "label", label: "Alignment: ", 40 | x: 1, y: 6, width: 1, height: 1 41 | }, 42 | trimAlign: { 43 | class: "dropdown", 44 | value: 5, config: true, items: {1,2,3,4,5,6,7,8,9}, 45 | x: 2, y: 6, width: 1, height: 1 46 | }, 47 | offsetLayers: { 48 | class: "checkbox", label: "Offset layers", 49 | value: true, config: true, 50 | x: 0, y: 7, width: 2, height: 1 51 | }, 52 | offsetModeLabel: { 53 | class: "label", label: "Mode: " 54 | x: 1, y: 8, width: 1, height: 1 55 | }, 56 | offsetMode: { 57 | class: "dropdown", 58 | value: "auto", config: true, items: {"auto","offset", "unique"}, 59 | x: 2, y: 8, width: 1, height: 1 60 | }, 61 | offsetValueLabel: { 62 | class: "label", label: "Offset:", 63 | x: 1, y: 9, width: 1, height: 1 64 | }, 65 | offsetValue: { 66 | class: "intedit", 67 | value: 0, config: true, 68 | x: 2, y: 9, width: 1, height: 1 69 | }, 70 | setStyle: { 71 | class: "checkbox", label: "Set Style", 72 | value: false, config: true, 73 | x: 0, y: 10, width: 2, height: 1 74 | }, 75 | styleNameLabel: { 76 | class: "label", label: "Name: ", 77 | x: 1, y: 11, width: 1, height: 1 78 | }, 79 | styleName: { 80 | class: "edit", 81 | value: "AI", config: true, 82 | x: 2, y: 11, width: 2, height: 1 83 | }, 84 | copyTimes: { 85 | class: "checkbox", label: "Copy times from selection", 86 | value: true, config: true, 87 | x: 0, y: 12, width: 2, height: 1 88 | }, 89 | removeActor: { 90 | class: "checkbox", label: "Remove layer name from actor field.", 91 | value: false, config: true, 92 | x: 0, y: 13, width: 3, height: 1 93 | } 94 | } 95 | } 96 | 97 | pasteAILines = (sub,sel,res) -> 98 | lines = LineCollection(sub,sel) 99 | maxLayer, endTime, startTime = 0, 0 100 | 101 | if res.copyTimes or res.offsetLayers 102 | lines\runCallback (_, line) -> 103 | endTime = math.max endTime, line.end_time 104 | startTime = math.min startTime or line.start_time, line.start_time 105 | if res.offsetMode == "auto" or res.offsetMode == "unique" 106 | maxLayer = math.max maxLayer, line.layer, 107 | true 108 | 109 | aiLinesRaw = string.split res.aiLinesRaw, "\n" 110 | aiLines = LineCollection sub 111 | firstSel, sel, lineCnt = sel[1], {}, #aiLinesRaw 112 | for i = 1, lineCnt 113 | aiLine = ASS\createLine { 114 | aiLinesRaw[i], lines, 115 | number: firstSel + lineCnt-i+1, 116 | style: res.setStyle and res.styleName or nil, 117 | actor: res.removeActor and "" or nil, 118 | start_time: res.copyTimes and startTime or 0, 119 | end_time: res.copyTimes and endTime or 0 120 | } 121 | 122 | -- trim drawings by moving the top left coordinate of the bounding box to the drawing origin 123 | if res.trimDrawing 124 | local off 125 | aiLine.ASS\callback (sect) -> 126 | off = sect\alignToOrigin res.trimAlign, 127 | ASS.Section.Drawing 128 | 129 | -- set alignment and position accordingly 130 | aiLine.ASS\replaceTags { 131 | ASS\createTag "align", res.trimAlign, 132 | ASS\createTag "position", off 133 | } 134 | aiLine.ASS\commit! 135 | 136 | aiLines\addLine aiLine, nil, true, true 137 | 138 | -- process layer numbers 139 | if res.offsetLayers 140 | local minAiLayer 141 | aiLines\runCallback (_, line, i) -> 142 | line.layer = switch res.offsetMode 143 | when "auto" 144 | minAiLayer or= line.layer 145 | line.layer - minAiLayer + res.offsetValue + maxLayer + 1 146 | when "unique" 147 | lineCnt-i + res.offsetValue + maxLayer + 1 148 | else line.layer + res.offsetValue, 149 | true 150 | 151 | aiLines\insertLines! 152 | return aiLines\getSelection! 153 | 154 | showDialog = (sub, sel) -> 155 | options = ConfigHandler dlg, depCtrl.configFile, false, script_version, depCtrl.configDir 156 | dlg.main.aiLinesRaw.text = clipboard\get! 157 | options\read! 158 | options\updateInterface "main" 159 | btn, res = aegisub.dialog.display dlg.main 160 | if btn 161 | options\updateConfiguration res, "main" 162 | options\write! 163 | pasteAILines sub, sel, res 164 | 165 | runSilently = (sub, sel) -> 166 | options = ConfigHandler dlg, depCtrl.configFile, false, script_version, depCtrl.configDir 167 | options\read! 168 | res = options.configuration.main 169 | res.aiLinesRaw = clipboard\get! 170 | pasteAILines sub, sel, res 171 | 172 | depCtrl\registerMacros { 173 | {"Open Menu", nil, showDialog}, 174 | {"Paste from Clipboard", nil, runSilently} 175 | } 176 | -------------------------------------------------------------------------------- /l0.ShakeIt.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Shake It" 2 | export script_description = "Lets you add a shaking effect to fbf typesets with configurable constraints." 3 | export script_version = "0.2.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.ShakeIt" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | depCtrl = DependencyControl { 9 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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.4.3", url: "https://github.com/TypesettingTools/ASSFoundation", 14 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 15 | {"l0.Functional", version: "0.5.0", url: "https://github.com/TypesettingTools/Functional", 16 | feed: "https://raw.githubusercontent.com/TypesettingTools/Functional/master/DependencyControl.json"}, 17 | {"a-mo.ConfigHandler", version: "1.1.4", url: "https://github.com/TypesettingTools/Aegisub-Motion", 18 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 19 | } 20 | } 21 | LineCollection, ASS, Functional, ConfigHandler = depCtrl\requireModules! 22 | {:list, :math, :string, :table, :unicode, :util, :re } = Functional 23 | logger = depCtrl\getLogger! 24 | 25 | -- Enums used in dialog 26 | signChangeModes1D = { 27 | Any: "Allow Any" 28 | Force: "Force" 29 | Prevent: "Prevent" 30 | } 31 | 32 | signChangeModes2D = { 33 | Any: "Any number" 34 | Either: "At least one" 35 | One: "Exactly one" 36 | } 37 | 38 | tagShakeTargets = { 39 | LineBegin: "Beginning of every line" 40 | ExistingTags: "Every existing override tag" 41 | TagSections: "Every tag section" 42 | } 43 | 44 | dialogs = { 45 | shakePosition: { 46 | { 47 | class: "label", label: "Shaking Offset Limits (relative to original position): ", 48 | x: 0, y: 0, width: 10, height: 1, 49 | }, 50 | offXMin: { 51 | class: "floatedit", 52 | value: 0, min: 0, step:1, config: true 53 | x: 0, y: 1, width: 3, height: 1 54 | }, 55 | { 56 | class: "label", label: "< x <", 57 | x: 3, y: 1, width: 3, height: 1 58 | }, 59 | offXMax: { 60 | class: "floatedit", 61 | value: 10, min: 0, step: 1, config: true 62 | x: 6, y: 1, width: 4, height: 1, 63 | }, 64 | offYMin: { 65 | class: "floatedit", 66 | value: 0, min: 0, step: 1, config: true 67 | x: 0, y: 2, width: 3, height: 1 68 | }, 69 | { 70 | class: "label", label: "< y <", 71 | x: 3, y: 2, width: 3, height: 1 72 | }, 73 | offYMax: { 74 | class: "floatedit", 75 | x: 6, y: 2, width: 4, height: 1, config: true 76 | value: 10, min: 0, step: 1 77 | }, 78 | { 79 | class: "label", label: "", 80 | x: 0, y: 3, width: 10, height: 1 81 | }, 82 | groupLines: { 83 | class: "checkbox", label: "Group lines by:", 84 | value: true, config: true 85 | x: 0, y: 4, width: 1, height: 1 86 | }, 87 | groupLinesField: { 88 | class: "dropdown", 89 | items: {"start_time", "end_time", "layer", "effect", "actor"}, value: 'start_time', config: true 90 | x: 1, y: 4, width: 1, height: 1 91 | }, 92 | { 93 | class: "label", label: "Shake interval: every", 94 | x: 0, y: 5, width: 1, height: 1 95 | }, 96 | interval: { 97 | class:"intedit", 98 | value: 1, min: 1, config: true 99 | x: 1, y: 5, width: 1, height: 1 100 | }, 101 | { 102 | class: "label", label: "line group(s)", 103 | x: 2, y: 5, width: 1, height: 1 104 | }, 105 | { 106 | class: "label", label: "Angle between subsequent line group offsets:", 107 | x: 0, y: 6, width: 10, height: 1 108 | }, 109 | { 110 | class: "label", label: "Min:", 111 | x: 0, y: 7, width: 1, height: 1 112 | }, 113 | angleMin: { 114 | class: "floatedit", 115 | value: 0, min: 0, max: 180, step: 1, config: true 116 | x: 1, y: 7, width: 2, height: 1 117 | }, 118 | { 119 | class: "label", label: "� Max:", 120 | x: 3, y: 7, width: 3, height: 1 121 | }, 122 | angleMax: { 123 | class: "floatedit", 124 | value: 180, min: 0, max: 180, step: 1, config: true 125 | x: 6, y: 7, width: 2, height: 1, 126 | }, 127 | { 128 | class: "label", label: "�", 129 | x: 8, y: 7, width: 2, height: 1 130 | }, 131 | { 132 | class: "label", label: "", 133 | x: 0, y: 8, width: 10, height: 1 134 | }, 135 | { 136 | class: "label", label: "Constraints:", 137 | x: 0, y: 9, width: 10, height: 1 138 | }, 139 | signChangeX: { 140 | class: "dropdown", 141 | items: table.values(signChangeModes1D), value: signChangeModes1D.Any, config: true 142 | x: 0, y: 10, width: 2, height: 1 143 | }, 144 | { 145 | class: "label", label: "sign change for X offsets of subsequent line groups.", 146 | x: 2, y: 10, width: 5, height: 1 147 | }, 148 | signChangeY: { 149 | class: "dropdown", 150 | items: table.values(signChangeModes1D), value: signChangeModes1D.Any, config: true 151 | x: 0, y: 11, width: 2, height: 1 152 | }, 153 | { 154 | class: "label", label: "sign change for Y offsets of subsequent line groups.", 155 | x: 2, y: 11, width: 5, height: 1 156 | }, 157 | signChangeCmb: { 158 | class: "dropdown", 159 | items: table.values(signChangeModes2D), value: signChangeModes2D.Any, config: true 160 | x: 0, y: 12, width: 2, height: 1 161 | }, 162 | { 163 | class: "label", x: 2, y: 12, width: 5, height: 1 164 | label: "of the X and Y offsets must change sign between subsequent line groups.", 165 | }, 166 | { 167 | class: "label", label: "", 168 | x: 0, y: 13, width: 10, height: 1 169 | }, 170 | { 171 | class: "label", label: "Random Number Generation", 172 | x: 0, y: 14, width: 10, height: 1 173 | }, 174 | { 175 | class: "label", label: "Seed:", 176 | x: 0, y: 15, width: 1, height: 1 177 | }, 178 | seed: { 179 | class:"intedit", 180 | value: os.time!, 181 | x: 1, y: 15, width: 2, height: 1 182 | }, 183 | repeatPattern: { 184 | class: "checkbox", label: "Repeat pattern every", 185 | value: false, config: true, 186 | x: 0, y: 16, width: 1, height: 1 187 | }, 188 | repeatInterval: { 189 | class:"intedit", 190 | value: 12, config: true, 191 | x: 1, y: 16, width: 1, height: 1 192 | }, 193 | { 194 | class: "label", label: "line group(s)", 195 | x: 2, y: 16, width: 1, height: 1 196 | }, 197 | }, 198 | shakeScalarTag: { 199 | { 200 | class: "label", label: "Shaking Targets:", 201 | x: 0, y: 0, width: 6, height: 1 202 | }, 203 | { 204 | class: "label", label: "Tag:", 205 | x: 0, y: 1, width: 1, height: 1 206 | }, 207 | tag: { 208 | class: "dropdown", 209 | items: table.pluck table.filter(ASS.tagMap, (tag) -> tag.type == ASS.Number and not tag.props.global), "overrideName", 210 | value: "\\frz", config: true, 211 | x: 1, y: 1, width: 1, height: 1 212 | }, 213 | LineBegin: { 214 | class: "checkbox", label: tagShakeTargets.LineBegin, 215 | value: true, config: true, 216 | x: 0, y: 2, width: 6, height: 1 217 | }, 218 | ExistingTags: { 219 | class: "checkbox", label: tagShakeTargets.ExistingTags, 220 | value: false, config: true, 221 | x: 0, y: 3, width: 6, height: 1 222 | }, 223 | TagSections: { 224 | class: "checkbox", label: tagShakeTargets.TagSections, 225 | value: false, config: true 226 | x: 0, y: 4, width: 6, height: 1 227 | }, 228 | { 229 | class: "label", label: "", 230 | x: 0, y: 5, width: 6, height: 1 231 | }, 232 | { 233 | class: "label", label: "Shake offset limits (relative to original tag value): ", 234 | x: 0, y: 6, width: 6, height: 1, 235 | }, 236 | absoluteOffsetMin: { 237 | class: "floatedit", 238 | value: 0, min: 0, step:1, config: true, 239 | x: 0, y: 7, width: 2, height: 1 240 | }, 241 | { 242 | class: "label", label: "< value <", 243 | x: 2, y: 7, width: 1, height: 1 244 | }, 245 | absoluteOffsetMax: { 246 | class: "floatedit", 247 | value: 10, min: 0, step: 1, config: true 248 | x: 3, y: 7, width: 2, height: 1, 249 | }, 250 | { 251 | class: "label", label: "", 252 | x: 0, y: 8, width: 6, height: 1 253 | }, 254 | groupLines: { 255 | class: "checkbox", label: "Group lines by:", 256 | value: true, config: true, 257 | x: 0, y: 9, width: 1, height: 1 258 | }, 259 | groupLinesField: { 260 | class: "dropdown", 261 | items: {"start_time", "end_time", "layer", "effect", "actor"}, value: 'start_time', config: true, 262 | x: 1, y: 9, width: 1, height: 1 263 | }, 264 | { 265 | class: "label", label: "Shake interval: every", 266 | x: 0, y: 10, width: 1, height: 1 267 | }, 268 | interval: { 269 | class: "intedit", 270 | value: 1, min: 1, config: true, 271 | x: 1, y: 10, width: 1, height: 1 272 | }, 273 | { 274 | class: "label", label: "line group(s)", 275 | x: 2, y: 10, width: 1, height: 1 276 | }, 277 | { 278 | class: "label", label: "Offset difference range between subsequent line groups:", 279 | x: 0, y: 11, width: 6, height: 1 280 | }, 281 | { 282 | class: "label", label: "Min:", 283 | x: 0, y: 12, width: 1, height: 1 284 | }, 285 | groupOffsetMin: { 286 | class: "floatedit", 287 | value: 0, min: 0, step: 1, config: true, 288 | x: 1, y: 12, width: 2, height: 1 289 | }, 290 | { 291 | class: "label", label: " Max:", 292 | x: 3, y: 12, width: 1, height: 1 293 | }, 294 | groupOffsetMax: { 295 | class: "floatedit", 296 | value: 10, min: 0, step: 1, config: true 297 | x: 4, y: 12, width: 2, height: 1, 298 | }, 299 | { 300 | class: "label", label: "", 301 | x: 0, y: 13, width: 6, height: 1 302 | }, 303 | { 304 | class: "label", label: "Shake offset constraints between subsequent line groups:", 305 | x: 0, y: 14, width: 6, height: 1 306 | }, 307 | signChange: { 308 | class: "dropdown", 309 | items: table.values(signChangeModes1D), value: signChangeModes1D.Any, config: true, 310 | x: 0, y: 15, width: 2, height: 1 311 | }, 312 | { 313 | class: "label", label: "sign change for tag offsets of subsequent lines.", 314 | x: 2, y: 15, width: 4, height: 1 315 | }, 316 | { 317 | class: "label", label: "", 318 | x: 0, y: 16, width: 6, height: 1 319 | }, 320 | { 321 | class: "label", label: "", 322 | x: 0, y: 17, width: 6, height: 1 323 | }, 324 | { 325 | class: "label", label: "Random Number Generation", 326 | x: 0, y: 18, width: 10, height: 1 327 | }, 328 | { 329 | class: "label", label: "Seed:", 330 | x: 0, y: 19, width: 1, height: 1 331 | }, 332 | seed: { 333 | class:"intedit", 334 | value: os.time!, 335 | x: 1, y: 19, width: 2, height: 1 336 | }, 337 | repeatPattern: { 338 | class: "checkbox", label: "Repeat pattern every", 339 | value: false, config: true, 340 | x: 0, y: 20, width: 1, height: 1 341 | }, 342 | repeatInterval: { 343 | class:"intedit", 344 | value: 12, config: true, 345 | x: 1, y: 20, width: 1, height: 1 346 | }, 347 | { 348 | class: "label", label: "line group(s)", 349 | x: 2, y: 20, width: 1, height: 1 350 | }, 351 | } 352 | } 353 | 354 | hasLineRotation = (line) -> 355 | styleTags = line\getDefaultTags nil, false 356 | return true unless styleTags.tags.angle\equal 0 357 | line\modTags {"angle", "angle_x", "angle_y"}, (tag) -> true 358 | 359 | groupLines = (lines, field, interval = 1) -> 360 | -- collect selected lines and group if desired 361 | groups = if field 362 | table.values list.groupBy(lines.lines, field), (grpA, grpB) -> grpA[1][field] < grpB[1][field] 363 | else [{line} for line in *lines] 364 | 365 | -- group fbf lines to get longer shake interval 366 | if interval > 1 367 | groups = [list.join unpack group for group in *list.chunk groups, interval] 368 | 369 | return groups 370 | 371 | applyPositionShake = (lines, groups, offsets) -> 372 | aegisub.progress.task "Shaking..." 373 | groupCount = #groups 374 | 375 | for i, group in ipairs groups 376 | aegisub.progress.set 20 + 80 * (i-1) / groupCount 377 | aegisub.cancel! if aegisub.progress.is_cancelled! 378 | 379 | for line in *group 380 | data = ASS\parse line 381 | pos, align, org = data\getPosition! 382 | modifiedTags = {pos} 383 | 384 | if pos.class == ASS.Tag.Move 385 | pos\add offsets[i][1], offsets[i][2], offsets[i][1], offsets[i][2] 386 | else 387 | pos\add offsets[i][1], offsets[i][2] 388 | if hasLineRotation data 389 | modifiedTags[2] = org 390 | org\add offsets[i][1], offsets[i][2] 391 | 392 | data\replaceTags modifiedTags 393 | data\commit! 394 | 395 | lines\replaceLines! 396 | 397 | collectTags = (lines, groups, tagName, targets) -> 398 | maxTagCountPerLine = 0 399 | groupCount = #groups 400 | 401 | tagsByGroupAndLine = for i, group in ipairs groups 402 | aegisub.progress.set 10 + 50 * (i-1) / groupCount 403 | aegisub.cancel! if aegisub.progress.is_cancelled! 404 | 405 | for line in *group 406 | tags = {} 407 | ass = line.ASS or ASS\parse line 408 | 409 | if targets.LineBegin 410 | -- get the tag section right at line begin, create one if it doesn't exist 411 | section = if #ass.sections > 0 and ass.sections[1].instanceOf[ASS.Section.Tag] 412 | ass.sections[1] 413 | else ass\insertSections(ASS.Section.Tag!, 1)[1] 414 | 415 | -- get the last matching override tag in that section, create one from style default if it doesn't exist 416 | tags[#tags+1] = section\getTags(tagName, -1, -1, true)[1] or section\insertDefaultTags tagName 417 | 418 | if targets.ExistingTags 419 | list.joinInto tags, ass\getTags tagName 420 | 421 | if targets.TagSections 422 | ass\callback (section,_,i) -> 423 | tags[#tags+1] = section\getTags(tagName, -1, -1, true)[1] or section\insertTags( 424 | section\getEffectiveTags(true).tags[tagName]), 425 | ASS.Section.Tag 426 | 427 | -- deduplicate tags we matched multiple times 428 | tags = table.keys list.makeSet tags 429 | -- sort tags by order of appearance in the line 430 | table.sort tags, (a, b) -> 431 | aSectionPosition = list.indexOf a.parent.parent.sections, a.parent 432 | bSectionPosition = list.indexOf b.parent.parent.sections, b.parent 433 | if aSectionPosition == bSectionPosition 434 | return list.indexOf(a.parent.tags, a) < list.indexOf b.parent.tags, b 435 | return aSectionPosition < bSectionPosition 436 | 437 | maxTagCountPerLine = math.max maxTagCountPerLine, #tags 438 | tags 439 | 440 | return tagsByGroupAndLine, maxTagCountPerLine 441 | 442 | getSingleSign = (mode, offPrev) -> 443 | ref = switch mode 444 | when signChangeModes1D.Prevent then offPrev 445 | when signChangeModes1D.Force then -offPrev 446 | else math.random! - 0.5 447 | return math.sign ref, true 448 | 449 | makePositionOffsetGenerator = (res) -> 450 | shakeRadius = math.vector2.distance 0, 0, res.offXMax, res.offYMax 451 | offXPrev, offYPrev, offX, offY = 0, 0 452 | -- allow user to replay a previous shake 453 | math.randomseed res.seed 454 | 455 | return (constrainAngle = true, rollLimit = 5000) -> 456 | for i = 1, rollLimit 457 | -- check if X sign change is subject to combined X/Y constraints 458 | xSign = if res.signChangeCmb == signChangeModes2D.One and res.signChangeY == signChangeModes1D.Force 459 | math.sign offXPrev, true 460 | elseif res.signChangeCmb == signChangeModes2D.Either and res.signChangeY == signChangeModes1D.Prevent 461 | math.sign -offXPrev, true 462 | -- otherwise use X-only constraints 463 | else getSingleSign res.signChangeX, offXPrev 464 | 465 | -- generate a new horizontal offset with the desired sign 466 | offX = xSign * math.randomFloat res.offXMin, res.offXMax 467 | xSignChanged = offX * offXPrev < 0 468 | 469 | -- check if Y sign change is subject to combined X/Y constraints 470 | ySign = if res.signChangeCmb == signChangeModes2D.Either and not xSignChanged 471 | math.sign -offYPrev, true 472 | elseif res.signChangeCmb == signChangeModes2D.One and xSignChanged 473 | math.sign offYPrev, true 474 | -- otherwise use Y-only constraints 475 | else getSingleSign res.signChangeY, offYPrev 476 | 477 | -- generate a new vertical offset with the desired sign 478 | offY = ySign * math.randomFloat res.offYMin, res.offYMax 479 | 480 | -- scale the current and previous offset vectors to the shake radius 481 | offXNorm, offYNorm = math.vector2.normalize offX, offY, shakeRadius 482 | offXPrevNorm, offYPrevNorm = math.vector2.normalize offXPrev, offYPrev, shakeRadius 483 | -- get the angle difference on the circle around the origin 484 | distance = math.vector2.distance offXPrevNorm, offYPrevNorm, offXNorm, offYNorm 485 | angle = math.degrees math.acos (2*shakeRadius^2 - distance^2) / (2 * shakeRadius^2) 486 | -- and check if is within the user-specified constraints 487 | if not constrainAngle or angle >= res.angleMin and angle <= res.angleMax 488 | offXPrev, offYPrev = offX, offY 489 | return {offX, offY} 490 | 491 | -- give up after so many rolls, because we're to lazy to actually do our maths 492 | -- and factor the constraints in when pulling our random numbers 493 | logger\error "Couldn't find offset that satifies chosen angle constraints (Min: #{res.angleMin}�, Max: #{res.angleMax}� for group #{i}. Aborting." 494 | 495 | makeSimpleOffset = (prev, min, max, signChangeMode = signChangeModes1D.Any, minDiff = 0, maxDiff = math.huge, rollLimit = 5000) -> 496 | for i = 1, rollLimit 497 | sign = getSingleSign signChangeMode, prev 498 | off = sign * math.randomFloat min, max 499 | diffToPrev = math.abs off-prev 500 | if diffToPrev <= maxDiff and diffToPrev >= minDiff 501 | return off 502 | 503 | logger\error "Couldn't find offset that satifies constraints Min=#{minDiff} <= #{prev} <= Max=#{maxDiff}." 504 | 505 | 506 | makeMultiOffsetGenerator = (res, count) -> 507 | -- this makes all initial offsets start with the same sign if sign change is enforced 508 | -- TODO: maybe offer an option to start with a random sign for every value 509 | offPrev = [0 for _ = 1, count] 510 | 511 | -- allow user to replay a previous shake 512 | math.randomseed res.seed 513 | 514 | return (applyConstraints = true, rollLimit) -> 515 | minPrevDiff, maxPrevDiff = if applyConstraints 516 | res.groupOffsetMin, res.groupOffsetMax 517 | else 0, math.huge 518 | 519 | offPrev = for i = 1, count 520 | makeSimpleOffset offPrev[i], res.absoluteOffsetMin, res.absoluteOffsetMax, res.signChange, minPrevDiff, maxPrevDiff, rollLimit 521 | return offPrev 522 | 523 | 524 | calculateOffsets = (seriesCount, generator, seed, repeatInterval = math.huge, startProgress = 0, endProgress = 100) -> 525 | offsets = {} 526 | 527 | for i = 0, seriesCount - 1 528 | aegisub.progress.set startProgress + (endProgress-startProgress) * i / seriesCount 529 | aegsiub.cancel! if aegisub.progress.is_cancelled! 530 | offsets[1 + i] = if i < repeatInterval 531 | generator i != 0 532 | else offsets[1 + i%repeatInterval] 533 | 534 | return offsets 535 | 536 | showDialog = (macro) -> 537 | options = ConfigHandler dialogs, depCtrl.configFile, false, script_version, depCtrl.configDir 538 | options\read! 539 | options\updateInterface macro 540 | btn, res = aegisub.dialog.display dialogs[macro] 541 | if btn 542 | options\updateConfiguration res, macro 543 | options\write! 544 | return res 545 | 546 | shakePosition = (sub, sel) -> 547 | res = showDialog "shakePosition" 548 | return aegisub.cancel! unless res 549 | 550 | -- fix up some user errors 551 | if res.offXMax < res.offXMin 552 | res.offXMin, res.offXMax = res.offXMax, res.offXMin 553 | 554 | if res.offYMax < res.offYMin 555 | res.offYMin, res.offYMax = res.offYMax, res.offYMin 556 | 557 | if res.angleMax < res.angleMin 558 | res.angleMin, res.angleMax = res.angleMax, res.angleMin 559 | 560 | -- check for conflicting constraints 561 | err = {"You have provided conflicting constraints: "} 562 | if res.signChangeX == signChangeModes1D.Force and res.signChangeY == signChangeModes1D.Force 563 | if res.angleMax < 90 564 | err[#err+1] = "Forced sign inversion for X and Y offsets require a maxium angle of at least 90�." 565 | if res.signChangeCmb == signChangeModes2D.One 566 | err[#err+1] = "Can't limit signs to only one of the X and Y offsets because sign changes are separately enforced for both." 567 | 568 | elseif res.signChangeX == signChangeModes1D.Prevent and res.signChangeY == signChangeModes1D.Prevent 569 | if res.angleMin > 90 570 | err[#err+1] = "Can't prevent sign inversion for X and Y offsets when the minimum angle is larger than 90�." 571 | if res.signChangeCmb == signChangeModes2D.Either 572 | err[#err+1] = "Can't change signs of either X or Y offsets because they are prevented for both." 573 | 574 | logger\assert #err == 1, table.concat err, "\n" 575 | 576 | lines = LineCollection sub, sel 577 | 578 | aegisub.progress.task "Grouping lines..." 579 | groups = groupLines lines, res.groupLines and res.groupLinesField or nil, res.interval 580 | aegisub.progress.set 10 581 | aegisub.cancel! if aegisub.progress.is_cancelled! 582 | 583 | aegisub.progress.task "Rolling dice..." 584 | -- generate offsets for every line group, but don't apply them immediately in case the generator fails 585 | offsets = calculateOffsets #groups, makePositionOffsetGenerator(res), 586 | res.seed, res.repeatPattern and res.repeatInterval or math.huge, 10, 20 587 | 588 | -- apply the position offsets to all line groups 589 | aegisub.progress.task "Applying shake..." 590 | applyPositionShake lines, groups, offsets 591 | 592 | shakeScalarTag = (sub, sel) -> 593 | res = showDialog "shakeScalarTag" 594 | return aegisub.cancel! unless res 595 | 596 | -- fix up some user errors 597 | if res.absoluteOffsetMax < res.absoluteOffsetMin 598 | res.absoluteOffsetMin, res.absoluteOffsetMax = res.absoluteOffsetMax, res.absoluteOffsetMin 599 | 600 | if res.groupOffsetMax < res.groupOffsetMin 601 | res.groupOffsetMin, res.groupOffsetMax = res.groupOffsetMax, res.groupOffsetMin 602 | 603 | lines = LineCollection sub, sel 604 | 605 | aegisub.progress.task "Grouping lines..." 606 | groups = groupLines lines, res.groupLines and res.groupLinesField or nil, res.interval 607 | groupCount = #groups 608 | aegisub.progress.set 10 609 | 610 | aegisub.progress.task "Collecting tags..." 611 | tagsByGroupAndLine, offsetCount = collectTags lines, groups, ASS.tagNames[res.tag][1], res 612 | 613 | aegisub.progress.task "Rolling dice..." 614 | offsets = calculateOffsets #groups, makeMultiOffsetGenerator(res, offsetCount), 615 | res.seed, res.repeatPattern and res.repeatInterval or math.huge, 60, 70 616 | 617 | aegisub.progress.task "Applying shake..." 618 | 619 | for g, group in ipairs groups 620 | aegisub.progress.set 70 + 30 * (g-1) / groupCount 621 | aegisub.cancel! if aegisub.progress.is_cancelled! 622 | 623 | for tagsByLine in *tagsByGroupAndLine[g] 624 | -- TODO: support tags w/ > 1 parameter 625 | tag\add offsets[g][t] for t, tag in ipairs tagsByLine 626 | 627 | line.ASS\commit! for line in *group 628 | 629 | lines\replaceLines! 630 | 631 | depCtrl\registerMacros { 632 | {"Shake Position", "Applies randomized offsets to line positioning.", shakePosition}, 633 | {"Shake Scalar Tag", "Applies randomized offsets to a specified scalar override tag.", shakeScalarTag}, 634 | } 635 | -------------------------------------------------------------------------------- /l0.SplitLines.moon: -------------------------------------------------------------------------------- 1 | export script_name = "Split Lines" 2 | export script_description = "Splits a line while maintaining appearance." 3 | export script_version = "0.2.0" 4 | export script_author = "line0" 5 | export script_namespace = "l0.SplitLines" 6 | 7 | DependencyControl = require "l0.DependencyControl" 8 | 9 | rec = DependencyControl{ 10 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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 | {"a-mo.ConfigHandler", version: "1.1.4", url: "https://github.com/TypesettingTools/Aegisub-Motion", 15 | feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, 16 | {"l0.ASSFoundation", version: "0.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 17 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 18 | "aegisub.re" 19 | } 20 | } 21 | 22 | LineCollection, ConfigHandler, ASS, re = rec\requireModules! 23 | logger = rec\getLogger! 24 | 25 | exampleExpr = [[step = n / 20 26 | x = x > step and x-step or n/5 27 | i + max(x, 2)]] 28 | 29 | hints = { 30 | interval: "The line will be split after the specified amount of characters." 31 | indexes: [[Enter a comma-separated 1-based list indexes into the original line. 32 | The line will be split before the characters at the specified indexes (1 is not a valid split index).]] 33 | expr: [[Enter the Lua function body that will determine the next index to split the line at. The function will run multiple times until the end of the original line 34 | has been hit. The index returned must be a number larger than the previously returned index (fractional values will be rounded up). 35 | A return statement will be prepended to the last line when none was found. Math library functions can be called without 'math.' prefix 36 | ]] 37 | } 38 | 39 | dlgs = { 40 | splitAtEvenInterval: { 41 | intervalLabel: class: "label", x: 0, y: 0, width: 1, height: 1, label: "Interval: " 42 | interval: class: "intedit", x: 1, y: 0, width: 1, height: 1, min: 1, value: 5, config: true, hint: hints.interval 43 | }, 44 | splitAtIndexes: { 45 | indexesLabel: class: "label", x: 0, y: 0, width: 1, height: 1, label: "Indexes:" 46 | indexes: class: "textbox", x: 0, y: 1, width: 10, height: 3, value: "2, 4, 9, 17", config: true, hint: hints.indexes 47 | }, 48 | splitAtCustomInterval: { 49 | varsLabel: class: "label", x: 0, y: 0, width: 5, height: 1, label: "Available variables:" 50 | varIdxLabel: class: "label", x: 1, y: 1, width: 4, height: 1, label: "i: current index into original line" 51 | varLenLabel: class: "label", x: 1, y: 2, width: 4, height: 1, label: "n: length of original line in characters" 52 | varCntLabel: class: "label", x: 1, y: 3, width: 4, height: 1, label: "s: current count of split lines" 53 | varNumLabel: class: "label", x: 1, y: 4, width: 4, height: 1, label: "x: a number initialized to 0 for your convenience" 54 | exprLabel: class: "label", x: 0, y: 6, width: 5, height: 1, label: "Expression:" 55 | expr: class: "textbox", x: 0, y: 7, width: 10, height: 5, value: exampleExpr, config: true, hint: hints.expr 56 | } 57 | } 58 | 59 | showDialog = (macro) -> 60 | options = ConfigHandler dlgs, rec.configFile, false, script_version, rec.configDir 61 | options\read! 62 | options\updateInterface macro 63 | btn, res = aegisub.dialog.display dlgs[macro] 64 | if btn 65 | options\updateConfiguration res, macro 66 | options\write! 67 | return res 68 | 69 | splitLines = (sub, sel, mode, arg) -> 70 | lines = LineCollection sub, sel 71 | lineCnt = #lines.lines 72 | toDelete = {} 73 | -- cleaning level, adjust \pos, write \org if required to maintain appearance 74 | config = {4, true, true} 75 | 76 | cb = (lines, line, i) -> 77 | aegisub.cancel! if aegisub.progress.is_cancelled! 78 | data = ASS\parse line 79 | 80 | splits = switch mode 81 | when "interval" then data\splitAtIntervals arg, unpack config 82 | when "tags" then data\splitAtTags unpack config 83 | when "indexes" then data\splitAtIndexes arg, unpack config 84 | 85 | lines\addLine split for split in *[line for line in *splits when line.ASS.textLength > 0] 86 | toDelete[#toDelete+1] = line 87 | aegisub.progress.set 100*i/lineCnt 88 | 89 | lines\runCallback cb, true 90 | -- TODO: check why line order is off by one when we do it the other way around 91 | lines\insertLines! 92 | lines\deleteLines toDelete 93 | 94 | splitAtEvenInterval = (sub, sel) -> 95 | if res = showDialog "splitAtEvenInterval" 96 | splitLines sub, sel, "interval", res.interval 97 | 98 | splitAtIndexes = (sub, sel) -> 99 | if res = showDialog "splitAtIndexes" 100 | indexes = [tonumber(i) for i in res.indexes\gmatch "(%d+)[%s\\n,;|/]*"] 101 | splitLines sub, sel, "indexes", indexes 102 | 103 | splitByChars = (sub, sel) -> 104 | splitLines sub, sel, "interval", 1 105 | 106 | splitAtCustomInterval = (sub, sel) -> 107 | res = showDialog "splitAtCustomInterval" 108 | return unless res 109 | 110 | -- initialize isolated environment for loaded expression 111 | env = setmetatable {s: 0, x: 0}, {__index: (tbl, k) -> _G[k] or math[k]} 112 | 113 | -- add missing return on last line and compile expression into a function 114 | pattern = re.compile "[ \t]*(return )?([^\n]+$)", re.NO_MOD_M 115 | expr = load pattern\sub(res.expr, "return $2"), nil, "t", env 116 | 117 | -- split lines 118 | splitLines sub, sel, "interval", (i, n) -> 119 | env.i, env.n = i, n 120 | env.s += 1 121 | expr! 122 | 123 | rec\registerMacros{ 124 | {"Split by Characters", "Turns every character into a separate line.", splitByChars}, 125 | {"Split at Even Interval", "Splits a line after every N characters.", splitAtEvenInterval}, 126 | {"Split at Custom Interval", "Specify an expression to determine split indexes.", splitAtCustomInterval}, 127 | {"Split at Indexes", "Manually specify a list of indexes the line will be split at.", splitAtIndexes}, 128 | {"Split at Tags", "Splits a line before every tag section.", (sub, sel) -> splitLines sub, sel, "tags"}, 129 | } 130 | -------------------------------------------------------------------------------- /l0.VerticalText.moon: -------------------------------------------------------------------------------- 1 | -- TODO: calc scale 2 | export script_name = "Vertical Text" 3 | export script_description = "Splits a line into vertical text." 4 | export script_version = "0.2.0" 5 | export script_author = "line0" 6 | export script_namespace = "l0.VerticalText" 7 | 8 | DependencyControl = require "l0.DependencyControl" 9 | depCtrl = DependencyControl{ 10 | feed: "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/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.4.0", url: "https://github.com/TypesettingTools/ASSFoundation", 15 | feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, 16 | "Yutils" 17 | } 18 | } 19 | LineCollection, ASS, Yutils = depCtrl\requireModules! 20 | logger = depCtrl\getLogger! 21 | 22 | absCos = (a) -> math.abs math.cos math.rad a 23 | absSin = (a) -> math.abs math.sin math.rad a 24 | alignOffset = (x, a) -> math.abs x / 2 * math.cos math.rad a 25 | 26 | averageGlyphMetricsByFont = {} 27 | getAverageGlyphMetrics = (fontName) -> 28 | return averageGlyphMetricsByFont[fontName] if averageGlyphMetricsByFont[fontName] 29 | 30 | font = Yutils.decode.create_font fontName, false, false, false, false, 100 31 | startChar, endChar = 65, 122 -- character codes within [A-Za-z] 32 | totalHeight, totalWidth, nonEmptyGlyphCount = 0, 0, 0 33 | 34 | for c = startChar, endChar 35 | x1, y1, x2, y2 = Yutils.shape.bounding font.text_to_shape string.char c 36 | if x1 37 | totalHeight += y2 - y1 38 | totalWidth += x2 - x1 39 | nonEmptyGlyphCount += 1 40 | 41 | averageGlyphMetricsByFont[fontName] = { 42 | w: totalWidth/nonEmptyGlyphCount, 43 | h: totalHeight/nonEmptyGlyphCount 44 | } 45 | return averageGlyphMetricsByFont[fontName] 46 | 47 | process = (sub, sel, res) -> 48 | aegisub.progress.task "Processing..." 49 | 50 | lines = LineCollection sub, sel 51 | finalLines = LineCollection sub 52 | 53 | cb = (lines, line, i) -> 54 | data = ASS\parse line 55 | -- split line by characters 56 | charLines = data\splitAtIntervals 1, 4, false 57 | charOffset = 0 58 | 59 | for charLine in *charLines 60 | logger\warn charLine.text 61 | charData = charLine.ASS 62 | -- get tags effective as of the first section (we know there won't be any tags after that) 63 | effTags = charData.sections[1]\getEffectiveTags(true,true).tags 64 | 65 | -- determine average width and height of glyphs for this font for vertical spacing generation 66 | averageGlyphMetrics = getAverageGlyphMetrics effTags.fontname.value 67 | -- with \an5 the type is centered between ascender and baseline, 68 | -- so we need to account for the descender and ascender separately 69 | metrics = charLine.ASS\getTextMetrics true 70 | charBounds = metrics.bounds 71 | descender = math.max charBounds[4] - metrics.ascent, 0 72 | ascender = math.max metrics.descent - charBounds[2], 0 73 | 74 | -- set \an5 75 | effTags.align.value = 5 76 | charData\removeTags "align" 77 | charData\insertTags effTags.align, 1 78 | 79 | -- calculate new position 80 | frz = effTags.angle.value 81 | charOffset += ascender * absCos frz 82 | logger\dump {metrics} 83 | effTags.position\add 0, 84 | charOffset + alignOffset(charBounds.h - math.max(charBounds[2]-metrics.descent, 0), frz) + alignOffset(metrics.width, frz+90) 85 | 86 | charData\removeTags "position" 87 | charData\insertTags effTags.position, 1 88 | 89 | -- set position for the next character 90 | spacing = 0.2 * averageGlyphMetrics.h * effTags.fontsize.value / 100 91 | charOffset += absCos(frz) * (charBounds.h + spacing + descender) + absSin(frz) * metrics.width 92 | 93 | charData\commit! 94 | finalLines\addLine charLine 95 | 96 | aegisub.progress.set i * 100 / #lines.lines 97 | lines\runCallback cb, true 98 | lines\deleteLines! 99 | finalLines\insertLines! 100 | 101 | depCtrl\registerMacro process 102 | --------------------------------------------------------------------------------