Fansub Guides

Timing:

I. Basics of Timing in Aegisub by unanimated

II. Timing without TPP + general guidelines for timing

III. Additional Notes for Timing - Shifting, Line Splitting/Joining, Timing Signs, Overlapping Lines, Pasting Over, Encoding Work Raw...

How to make xvid keyframes

Typesetting:

ASS Tags - explains all Aegisub tags

Typesetting guide by unanimated

Guide to using mkv tools by unanimated

Guide to making chapters by RHExcelion (Just use this script instead.)

Guide to using xdelta files by unanimated

Aegisub:

Download latest Aegisub here

General Tips for Using Aegisub

Scripts for Aegisub

IRC guides:

IRC Guide #1 by Orcus [for mIRC]

IRC Guide #2 by unanimated [for KVIrc] Basics of Timing in Aegisub

As you should know if you're reading this, timing is about setting start and end times to each line of a script. In essence simple enough, but there's a hell of a difference between somehow herp derp timing it and doing it right. You need to do it right.

First thing to do is to download Aegisub and set it up properly (!) and become familiar with it.

In order to time, you need to load video, audio, and script. A/V is easy, but with the script you can mess up. If your script is already in .ass format [and I mean for real, not just some dumbass renaming a .txt to .ass], you just Open the file, or drag it onto Aegisub. If it's a .txt or the script is on the pad, rather than Opening it, you copy the text, click on that empty line in a new script in Aegisub, and paste. You should then see a script with zeros for times.

If you happen to get a script with character names, like this: Shana: Shut up! you Open it and put a colon as Actor Separator when asked. Then you should see names in the Actor field and script in the text field. Always check the whole script if you got it in this form because there might be stuff like this:

Now, we don't give a fuck about the "{missing line here" in the actor field, because that field is useless; the problem is that we have "}" before Maeda, which will show on screen. So you have to delete that. The line in the script was: {missing line here: }Maeda will not ally with anyone from now on {??? TLC} Since the colon acts as separator, it got split the way you see.

Aside from that, until you figure out how to load files without making mistakes, you should keep your eyes open for question marks that seem out of place, because it just might be that you failed to import a script in Unicode encoding properly, and those question marks were something else originally. We can't have that, so figure out what you did wrong and do it again right.

Timing consists of Rough Timing, (TPP), and Fine Timing.

Rough-Timing is where you set the start/end times on the audio track. TPP - Timing Post-Processor - is a tool that adds lead in/out, links lines, and snaps to keyframes. Fine-Timing is where you watch the video after rough-timing, looking for and fixing any errors.

Rough and fine-timing takes about 20-40 minutes each, once you get some practice, which might take a few dozen episodes. Your first attempts, however, are expected to take as long as 3-5 hours, so be ready for that. TPP takes 0.0002 seconds. [Yeah, I counted them.]

You may or may not wanna use TPP. Some people love it, some hate it. It's totally up to you, so try both and see. For now I'll explain stuff assuming the usage of TPP and get to timing without TPP later.

Before you start timing, I recommend to use these settings: Now you just use the left mouse button to set start times and right button to set end times for each line. Pressing G applies changes and gets you to the next line. Rough timing means you set the times to the exact start and end of the audio for each line. It will look like this:

Now, let's talk about all the buttons and stuff. On the right you have 3 vertical sliders. The first one zooms into the audio horizontally, 2nd one vertically, 3rd one is volume. You can click the |_| under 2 and 3 to link them together if you prefer that.

The buttons under the audio... I don't actually click on any of them ever, but I use the keyboard shortcuts for some of them. First 5 are pretty useless. You just need to play/stop the audio with a key, not mouse. Go to Options-Hotkeys and set "Audio Play or Stop" to whatever key will be the most convenient for you. This will be the most used key along with G, so keep them close. This will play the audio from your current start time to end time.

Next one you'll need is the one that plays 500ms after selection. Finding the start of a line is usually easy. Most of your time will be wasted on figuring out where a line ends. You can set the end far enough and play the whole thing. That will be the most reliable, but slow. You can probably start with just that, but later you'll wanna get faster. So you'll set the end to where you think it will be and play the line to check. If there's a pause before next line, it will be easy, but in a continuous dialogue you may not be sure your guess was right until you hear what's after your end time. So you use the key that plays 500ms after. And to make it even faster, once you get the hang of it, you won't wanna play the whole line each time, you just need to find the end, so you need the key for the last 500ms. You guess-set the end, use keys to play 500ms before and after it, and that should give you an idea about whether you got it right.

Sometimes it may be useful to play 500ms before the start, to check you didn't miss a silent sound or something, and you may need a key shortcut for jumping to previous line if you realize you made a mistake. Rather than using default settings, figure out which of these things will be useful to you, and set the keys so that you can easily use all of them without looking at the keyboard.

Another very useful thing is committing changes without jumping to the next line. Set a key for "audio/commit/stay" in Options.

The button before last switches the audio to spectrum analyzer mode. Try it out, and see what works for you. Again, some timers can't work with waveform while others can't work with spectrum, so up to you to figure out which you prefer.

Now you know how to rough-time. Don't forget to save often enough, though the autosave works pretty well now.

When your script is rough-timed, it is far from presentable to the viewer. The subs will look like shit and will be unreadable because text will disappear too soon, lines will flicker etc. So Fine Timing and/or TPP is about fixing that.

Text has to appear a little before the audio and stay on screen a little after the person is done talking, to give the viewer enough time to read. When two lines are close to one another, they should be linked together so that text doesn't disappear for just a few milliseconds, because that looks bad. We call this flickering subs.

The last thing that needs to be considered is keyframes. In case you don't know, a keyframe is a frame where a scene changes in the video [like a camera cut]. If subs end just a few frames after a keyframe, it looks really bad. We call that a bleed. You need to avoid those like the plague. Basically, if anything starts or ends near a keyframe, it should be snapped to it. You can see them as the purple lines in Aegisub. However, note that these aren't always precise, so there may be scene changes that you won't see on the audio track, and you must notice those when watching the video.

If you scroll up to the last picture, you'll see that 2 lines end just before a keyframe. They will have to be snapped to those keyframes. The fine-timed lines will look like this: So how do you get from rough-timed to fine-timed? Either you use TPP, then watch it and fix whatever is wrong, or, if you're not using TPP, you fine-time it in the first place, basically skipping rough-timing completely.

So how to use TPP and what it does.

TPP adds those lead ins and lead outs, links lines, and snaps to keyframes. The advantage is, it does that in a split second. The disadvantage is, it can actually ruin a few things, which is why many timers choose not to use it. And if you are gonna use it, you need the right settings.

I've done some experiments with different settings and got the best results with roughly this:

You can probably use 100 for lead in. I don't like too much lead in, so this works for me. Generally lead in should be about 80-100, lead out 250-350, and line linking 400-500, with the slider about 90% to the right.

In case you have trouble understanding how this works, once you click OK, it adds 80ms lead in and 300ms lead out to ALL lines, without them overlapping [so if they're closer than 380, you won't get the full values]. Then for any 2 lines that are 450ms apart or closer, it links them, turning 90% of the gap into lead out for the 1st and 10% into lead in for the 2nd. Then it snaps to keyframes any lines that 1. start 250ms or less before a keyframe 2. start 250ms or less after 3. end 400ms or less before 4. end 500ms or less after.

Because this is a mathematical calculation with no judgement of what's really going on, there will be places where these values add up to something you don't really want. Therefore you need to do Fine-Timing, which is just as important as rough-timing.

What it means is that you watch the video and focus on the subs, catching anything that's not right. Note that TPP can only "see" keyframes that have those purple lines, so depending on the encoding settings of the video you may have a lot of scene changes without the purple lines, which means TPP won't see them and there will be bleeds. So one of the main things you have to look out for when fine-timing is bleeds. It takes some training and requires your full attention. Whenever you see a bleed, or just spot that something felt off about the start/end [usually end] of the line, go back to that line and use arrow keys to play it frame by frame to check if the line really started/ended on the frame it was supposed to. If it's off, you need to fix it. I'll talk later about how to set the time to the exact right frame.

An example of poorly fine-timed script in somebody's first attempt at a full episode. You can see 5 cases of flickering and one bleed here. All of that has to be fixed.

One more thing about bleeds - if only one syllable or a short word goes beyond a keyframe, you generally want it snapped to the keyframe [case in point - picture above]. When watching, you won't even notice there was a short sound after the scene change, and the subs look a lot better if they don't intrude into the next scene.

Aside from bleeds, you must notice if some lines end too soon, start too late, if there's flickering, or if you actually missed some silent words, or simply clicked somewhere totally wrong by mistake. It may also happen that when rough-timing, you mistime some lines and at some point end up being one line off. All of that needs to be fixed during fine-timing.

If you've watched the whole video and fixed everything that was off, you're done.

Just one more thing that will drive you nuts in the beginning - overlapping dialogue. When two or more characters are speaking at the same time, it can be quite difficult to time everything correctly. I recommend timing one character first, then the other. You can even rearrange the lines to put one character first. It will be better for linking, too. Since it's difficult to read many lines at once, you may want to use more lead in/out and linking for overlapping dialogue. When 2 people are speaking and subs keep disappearing, you might end up not being able to read anything.

Aside from just timing it, there's the issue of where to place all the lines. If you leave them all in default position, it'll be hard to tell which lines are whose, and they may stack on top of each other and end up in the middle of the screen. We'll talk about that later, for now you just need to time everything correctly.

Next part: Timing without TPP Timing in Aegisub Without Using TPP

You've read the Basics of Timing in Aegisub [hopefully], so here's the next part.

I've tried experimenting with TPP, and I can get it to produce somewhat decent results, but it just lacks human intelligence and adaptability, and tends to screw up things more than I'm willing to deal with. You may time a certain way to make up for the TPP imperfections, but then again you can also time a certain way so that you don't need it at all. In the end rough-timing with TPP takes me longer than without it, because I have to be more precise with hitting the exact start/end of the audio to make sure the imperfections in that won't add up with TPP settings. It also adds to fine-timing because there are a lot more things to correct after TPP than after my own rough-timing.

Even if you do use TPP though, this will still give you some guidelines for the various thresholds. I'm basically describing what you want to end up with. If you use TPP, you want to get these results after applying it, if you don't use it, you simply use these tips for "rough" timing. I say "rough" because rather than rough- & fine-timing, if you don't use TPP, you're still doing two passes, but it's something more akin to fine-timing and QC of your timing.

It used to be pretty rare not to use TPP, but nowadays almost everyone times without it. It requires a different approach to the first pass, so I want to explain that, so that new timers can get a better idea of both ways and decide which one they want to go with.

The idea here is that you apply lead in, lead out, and do line linking and keyframe snapping in the first pass. TPP users argue that TPP makes things faster, but I don't really see a difference between clicking the start time exactly at the start of audio, and clicking it a little bit before to create the lead in. In fact this way seems faster to me because I don't have to be too precise, since no additional actions will fuck up my lead in once I've applied it. Same with lead out.

Line linking requires a bit of attention and thinking, but you'll get used to it quickly. Where TPP settings would be for example 100ms lead in, 300ms lead out, and 450ms for linking, you get a total threshold of 850ms between end of one line and start of the next. The audio in aegisub has those blue vertical lines in 1s intervals, so you can easily see how much time there is between 2 lines. We don't need to calculate it too precisely. Let's say the threshold is somewhere between 700 and 900ms. If the lines are closer than that, you link them. You want them linked somewhere around 80-90% toward the second line. For example if the gap is 800ms, you'll have 650-700ms lead out and 100-150ms lead in. More details on linking later.

The last thing is keyframe snapping. Here is where I think your eyes will do a much better job than any TPP settings. While looking at the audio track you can judge lead in/out, linking and snapping all at once, without worrying about how these things will stack up.

Let's look at end times first. If the line ends before a keyframe, there shouldn't be less than 500ms between the end time and a keyframe. Anything under 500ms should be snapped. The upper limit would be around 800-1000ms. In other words when the audio ends 900ms before a keyframe, you can still snap it, but you can also apply 300ms lead out and leave 600ms gap before the keyframe. At 1000ms you should only snap if the line is long and requires more time to read. This is something that TPP can never judge.

Now, if the line ends after a keyframe, it's even more about human judgement instead of rigid settings. Usually if only one syllable goes beyond the keyframe, you should snap it, even if the last syllable is long, like part of a scream. If you try watching it, you'll see that in most cases you'll barely notice that the subs disappeared a bit earlier, because the disappearance on a keyframe just looks natural. In fact, subs staying on screen a bit after a keyframe look weird even in cases where you have no choice but to do it anyway. So this should be avoided as much as you can. Even if a whole word is after the keyframe, it may be ok to snap, as long as you get to read the whole line comfortably. The only reliable way to judge this is by watching, so that'll be left to fine-timing, but in rough-timing you can lean more toward snapping. Only if that looks bad when watching it or if you can't read the line before it disappears, you'll extend the line when fine-timing.

Now start times. A line starting just before a keyframe isn't much to worry about. Firstly, it doesn't happen too often, and secondly, it doesn't look nearly as bad as the other keyframe related things. So in most cases you can disregard the keyframe and just make a regular lead in. Only if the start of audio is less that about 5 frames before a keyframe, you should snap to it. If you watch a few lines of this kind, you will see that it looks worse if a character starts talking and you go "subs fucking where?" than if subs appear normally and then there's a keyframe. You may also note that at the start of a line you're busy reading subtitles, so you don't pay that much attention to scene changes, while at the end of a line you're probably finished reading and notice bleeds easily. If a line starts after a keyframe, there's roughly a 300ms threshold under which you should snap. A gap under 300ms is what you could call flickering subtitles. That's bad. Lead in over 300ms looks odd because subtitles appear when nobody's talking.

That's it for the basics. As far as I'm concerned, all of this doesn't really take any longer than timing to exactly the start/end points of the speech.

Now just a few tips for fine-tuning your timing. This won't work much with TPP because unless you have an extremely efficient calculator in your brain, you don't really know what TPP will do with all those times. Then again if you could calculate all that, you wouldn't need TPP in the first place.

It should also be made clear that if you are gonna use TPP, you have to time like this:

No lead in/out, linking or keyframe snapping.

Without TPP you do all those things immediately.

There are several things you can consider when deciding whether to apply more or less lead in/out, or which lines to link or snap. One would be the length of the text compared to the length of the audio. If the audio is long, like characters talking slowly or stammering, you can apply less lead in/out because you will have plenty of time to read the line. If they're talking really fast or the translation is really wordy, you will need more - sometimes even over 1 second lead out may be justified.

If the translation is only 1-2 words, you can even go without lead in, though some lead out should always be there [unless keyframe], and generally no line should be under about 600ms. With short, stand alone lines, make sure to always have more lead out than lead in.

Line linking is where you can use even more intuition. When you're around the threshold for linking and you're deciding whether to link or not, there are a few factors that will help you. One, as mentioned, is the length of both lines. If they both have enough time for reading, you can make the lead in/out a bit shorter so that the gap doesn't seem that awkward. If it's wordy or talking fast, you can link even when the gap is over 1 second.

You should be more inclined to link when both lines are a part of one sentence. When the 2 lines are spoken by 2 different characters, you may lean toward less linking than when it's the same person.

As should be clear by now, linking is different when there's a keyframe in there. Say you have an 800ms gap between lines. With no keyframe, you're most likely gonna link them together. But if there's a keyframe 200ms after the end of line 1, you'll snap that one and apply ~100ms lead in to the 2nd. That will leave 500ms between the keyframe and start of line 2, which is fine.

Line Splitting

Timers should also split lines when needed. Translators don't always pay attention to how much they write in one line, and editors often don't change any of that, so the timer may have to fix some things. If the translator puts two sentences in one line, and there's a pause in the audio, this line should be split unless both together are really short. If it's one sentence, but there's a 2 second pause in the audio, it should be split as well in most cases [unless... the sentence is actually so short that there really isn't anywhere to split it]. You can also split a line when the character reveals the important part of the line at the very end, as long as there's a reasonable pause. This can also be solved with alpha timing, but don't overuse it.

Joining lines

Sometimes the opposite is needed. The translator may split a line pretty logically but you'll find that the audio doesn't have any pause where you could link those lines. You may try to estimate a good place, but subtitles changing when there's no pause in the audio just doesn't look good. As long as the result isn't a 3-liner on screen, it is best to join these lines into one. If you join them, aegisub will insert \N between the two original lines, so we usually nuke those. [If, however, you work for Daiz, keep it and add a few more.]

You may also wanna join lines when you have a longer audio segment, pause, and shorter audio segment, while at the same time you have 2 lines where the first one is shorter, and you can't split them differently. Linking them at the pause may cause the 2nd one to disappear too quickly. Linking them elsewhere brings us back to the problem we've just described. So again, if they don't cause a 3-liner, you can join these to avoid awkward linking.

That's [not] all. Some more here.

A video capture of my timing with written notes: [Commie] Timing Without TPP - 01 [137066CF].mkv

A sequel with no commentary: [Commie] Timing Without TPP - 02 [4EDC5E9E].mkv This is just raw video for reference. 240 lines timed in about 14 minutes.

Part 3 with the actual audio from Aegisub: [Commie] Timing Without TPP - 03 [987359BC].mkv Half an episode. Roughly 200 lines / 16 minutes.

[ Mediafire links: Part I • Part II • Part III ]

As for how accurate this 3rd one was, here's what I had to fix in the 2nd pass:

Dialogue: 0,0:05:44.32,0:05:46.82,Default,Guiche,0000,0000,0000,,No matter what dangers we may face, - missing first word, because silently spoken, not well visible on audio track » 0:05:43.87,0:05:46.82

Dialogue: 0,0:05:51.18,0:05:5,Default,Malicorne,0000,0000,0000,,Was this really something that big? - same thing - start should be snapped to keyframe » 0:05:50.66,0:05:53.09

That's all. Additional Notes for Timing

Once you learn the basics of timing, there are several more things you should learn, or at least take note of.

First thing you will definitely need is shifting, mostly for OP/ED.

Regarding OP/ED, you copy them from a previous episode. Each episode has the OP/ED at different timecodes, usually. So you need to shift them in the right place. There are two methods. The second one, described further below, is much better. This is the basic one, probably more suitable for shifting the whole script:

You'll go to "Timing > Shift Times..." in the menu or use this icon: v

Set how much time you want to shift it by, whether backward or forward, and for OP/ED it will be only the selected rows. So obviously first you select the OP lines, shift, then same with ED. How to know how much you need to shift? Find where the first line should start and compare with the timecode of the line from previous ep. Then shift roughly by that time, to get close enough. Then select first line, and go frame by frame to where exactly you want it to start.

Check the first value highlighted here. That's how much you need to shift & in what direction^ So you'll be shifting by 0:00:00.90 forward. Check a few lines to make sure it worked right. [This is important!] If the song is only timed to audio, this should be easy enough. Some may be frame-timed though, ie. at least some lines are timed to specific frames, either scene changes or to some text appearing on screen. Here you have to shift precisely to the right frame. So if you check a few lines and see you're off, go frame by frame to see how many frames you're off. One frame would be 0:00:00.04 or you can use the Frames mode.

Always check your work after you're done.

If you have a CR script and need to shift it to your raw, it's similar, but you shift all lines. [I strongly recommend to time from scratch, though. CR = horrible.] There may be that 10-second screen after OP that CR has and your raw doesn't, so before OP you may be just a few frames off, but after OP 10 seconds. So select each part and shift accordingly. If you do this with the whole script, some lines may end up 1 frame off while others will be ok. I recommend running the TPP with these settings on Default style:

This will snap to keyframes anything that's 1 or 2 frames off. (This is from old aegisub. Use 80ms instead.) As for signs, you should check them all and fix manually as needed.

Two notes: - Often CR scripts have such bad timing that it's better to zero the times and time it from scratch. Like I said, I recommend to always do that. - Every time you shift any large chunk of script, you need to fine time it.

Shifting - Method 2:

This method should be used whenever possible. Usually it's easier anyway. Select the whole OP [or ED, or whatever you're shifting], find the frame where you think the active line should start on this raw... and click this icon:

That will shift the whole OP, making the active line start at your current frame. If again you find you're a frame or two off, select the whole OP, go that frame or two forward or back, and click that icon again.

If the OP/ED is frame-timed, you should recheck all lines, because even if the whole thing is shifted correctly, a random line may end up 1 frame off due to video interlacing [or something like that]. So you need to fix any such lines manually [sucks].

Line Linking

If you need to link two lines, you can either do it on the audio track, or you can select the lines, right click, and choose "Make times continuous."

There are 2 options: 1 of them moves the end time of the first line to the start time of the second, the other does the opposite. This works even when selecting more than 2 lines. It also works not only when the lines aren't linked, but also when they're overlapping [thus getting rid of the overlap].

Line Splitting / Joining If you get a crappy script, which may be fairly often, you may have to split/join some [or even many] lines. This will be fairly common with CR scripts, or scripts from some translators who focus on translating and pay little attention to line lengths.

Some lines are too long and should be split in 2, other times there are 2 lines where there should only be one, for no apparent reason. Take a look at this example:

You can see on the audio track that there's a pause in the audio, about 600ms. You can also see a comma separating 2 clauses in the sentence. So that's a good example where you can split the line. You right click at the mark after the comma and select Split like you see. It will estimate the splitting point based on the length of the sentences, so in 99% cases you'll have to correct that.

When to split lines?

1. Whenever you get a 3-liner. No 3-liners allowed, ever. 2. When there are 2 full sentences in the line. [Unless they're both short and no significant pause between them.] 3. When it's a long sentence with a clear pause in the middle and a suitable place to split in the English line [example in picture ^]. 4. When the audio is really long, with pauses, even if the TL is relatively short. There shouldn't be any pauses over 1 second in a line.

Where to split lines?

You should split where it makes sense. If there's a period, it's clear. Same with a comma. Or ellipsis, dash, etc. If not, then any place where you might pause in the English sentence. If you split a line in two, either of those two shouldn't look too retarded on its own. When you split, don't add ellipses everywhere! If there's no comma or anything, you may add an ellipsis at the end of the first, but leave the 2nd line as is. Personally I wouldn't add them anywhere. Ellipsis is the kind of thing crappy editors put everywhere because they don't know any better.

Sometimes you want to do the opposite and join two lines.

When to join lines?

This is not as common as splitting but there are some cases.

1. When there's no pause in audio and both lines are short enough to be joined comfortably. Sometimes CR splits lines for no good reason. It is awkward when lines change with no pause in audio, so try to avoid that. 2. When one or both the lines don't get enough screen time for reading. If you join them, it will be easier to read the whole thing. Of course this has to work without ending up with a 3-liner.

How to join: Select the 2 lines, right click, "Join (concatenate)". This here is an example for illustration - not to say these lines should be joined. They could be, but they're ok as they are. Matter of preference.

You may also take note of the other options in this menu and try them out. "Create audio clip" saves a wav sample of audio for selected lines.

Overlapping Lines

Something you'll be dealing with every now and then is overlapping dialogue. What to do when 2 people are speaking at the same time? If at all possible, avoid overlaps. If one person gets cut off by another and the overlap is short, make the lines switch exactly when the 2nd one starts. Whenever you can time the lines so that they're all readable without overlapping, do it that way.

When there are two separate conversations going on at the same time, or there's TV or train announcements in the background, use {\an8} for the less important one. If this is the case, I recommend rearranging the lines not by time, but one conversation first and then the other. Otherwise TPP will mess it up - line linking won't work because 2 consecutive lines will be from 2 different dialogues. You can use Swap from the right-click menu to swap 2 lines, or just copy one or more lines, paste them in the place you need, and delete the originals.

When two people talk at the same time and it's not two different conversations, like when they're talking to each other without really listening, you'll have overlaps. Just as this kind of a thing is difficult for you to time, it is also difficult for the viewer to read, especially if badly timed, so you need to time this to make it as readable as possible, which means you'll have to bend some rules. For one, the lines should probably last a bit longer than usual. They shouldn't start/end a few frames after one another, as that looks bad. It is up to you to figure out how to do it so that it's clear which line belongs to which character. You should also try to not have a line hanging in the overlapping position while there's no line in the regular position under it. At least not for long.

If you have some typesetting skills, there are various ways to make this easier for the viewer. You can temporarily change the outline color for one speaker - but only slightly, no fireworks. Just make it a bit brighter or something.

En elegant way to deal with this is to always have a new line show up in the default, bottom position, and move the previous, still visible line above it. Yoy would use \move for the first line, with start time a bit before the 2nd line appears, and end time when the 2nd appears. Make sure the tag has precise coordinates, X is always the same [normally 640], and Y moves from default position to where a 2nd line would normally be. For example you have a line from 01:20.10 to 01:23.50, and another from 01:22:10 to 01:25.60. They overlap from 1:22.10 to 1:23.50, ie. 1.4 seconds. The first one would then have something like \move(640,685,640,637,1500,2000). The 2nd line starts 2 seconds after the first, so by that time the first one has to be out of the way, therefore the 2000. You need about 500ms to make the movement not too slow and not too fast. This way you always know which line is which because they will always appear at the bottom, timed to audio. The movement also tells viewers that a new line is coming & the old one will stay for a bit more, so they know roughly how much time they have for reading. You can have regular lead-ins and let the lines that move up have more lead-out. If you use TPP, then this whole thing should be done after that because hell knows what TPP would do to this. If both overlapping lines are very short, like 1 character screaming "Idiot!" and the other "Moron!", you can use margins to have each on one side. Change left margin to ~500 for one and right margin for the other, and you'll have each line under the character saying it.

Or if you're creative, you can do all kinds of other things. [See horizon 09, Aoi Toori & "Diving"]

Timing Signs

If you only do timing and not typesetting, you should at least know how to time signs correctly. They need to be frame-timed precisely. From the typesetting guide:

...you rough-time the sign first, whether on the audio or by inputing the timecode you got from the translator/typist/editor. Then you go frame by frame with arrow keys till you find the first frame where the sign appears [the relevant line must be selected in the script]. Then you click the first of the blue icons here:

That sets the start time. Use arrow keys to check if you did it right. Then navigate to the last frame the sign is visible on, and click the second one. Again try if it's right. The first one sets the time at the start of the visible frame, the second one at the end, so if you use both at the same frame, the sign will be visible on that frame, in other words the duration of the sign will not be zero. ...

Many signs start/end at a keyframe so you can use the audio track for those, for others you'll need this method.

If a sign is fading in/out, you time it from the start of fade-in to the end of fade-out. When you're only timing and not typesetting, comment the signs after timing them. [If you don't know how to comment, check the Aegisub page]

Pasting Over

Pasting over means you paste several lines of text onto a timed script in Aegisub, replacing the text, but keeping the timing. This is usually needed when you're timing an unedited script and the editor is editing on the pad at the same time. When both are done, you have timed and unedited script in Aegi, and edited untimed script on the pad. You need to paste the edit over the timed one.

To do this you either go to "Edit > Paste Lines Over..." in the top menu or use Ctrl+Shift+V. You only need to select the first of the lines you're pasting over. Aegisub will give you a menu where you can choose what you want to paste. Usually you'll just need Text. If there were no changes made to the line count - splitting or joining, this will be easy. Usually there is some of that, though. In that case, you'll have to do it piece by piece, like 20 lines at a time, and always check if you still have the same line at the end. You can use Ctrl+Z / Ctrl+Y to see clearly what has changed.

If needed, you can paste over some of the other things in the menu, like Start Time / End Time. Should be self-explanatory.

Workraw / Keyframe file

Something that will be quite useful to you as a timer is to encode a workraw or create keyframes. If you only have a premux, there may be many scene changes without keyframes on your audio track in aegisub, which makes fine timing more difficult. Workraw is encoded with different settings, among other things to have more keyframes.

Here's how you encode scxvid keyframes.

After some experimentation I'd say x264 works almost just as well as xvid with good settings [but xvid should be a bit better].

If you wanna use x264, use this: x264 --video-filter resize:848,480 --crf 28 --ref 1 --bframes 1 --me hex --subme 1 --scenecut 98 --min-keyint 1 --keyint infinite -o %1_wr.mkv %1

Create a batch file, for example wr.bat, and put this line ^ in it. Put this in your working folder for timing, along with x264.exe [8-bit]. Then open cmd.exe from the folder and type "wr another01_premux.mkv" where another01_premux.mkv is your premux, obviously.

If your PC is really slow and this takes more than 10 minutes to encode, use this: x264 --video-filter resize:640,360,method=bilinear --qp 30 --preset ultrafast --scenecut 98 --min-keyint 1 --keyint infinite -o %1_wr.mkv %1

If that doesn't use 100% of your CPU, you can make it faster by encoding in 2 threads. Read more here.

Thresholds

Some quick answers / brief summary. This is not what you should just put in TPP and think you're OK. This is what the results should be. Keyframe means an actual scene change, whether there's a purple line on the audio track or not.

Lead in: 80-120ms.

Lead out: 250-350ms. Can be made longer when needed.

Line linking: 450-500ms [ie if the gap is under that, link; if there's a keyframe in between, the following 2 apply]

From End Time to keyframe: 400-500ms [ie if the gap is smaller, snap to keyframe]

From keyframe to Start Time: 300-350ms [ie if the gap is smaller, snap to kf; if larger, leave at least 300ms gap before lead-in]

You should never have a gap under 300ms between any 2 lines!

Line ends just after keyframe: 500ms. Snap these as much as possible. If it's just one syllable over the keyframe, snap. If it's a short word, try if you can read the line when snapped. If you can read it, snap. Snapped always looks better.

You shouldn't have lines ending within 500ms after a keyframe! How to make xvid keyframes

Things you need:

1. Install Avisynth

2. SCXviD.dll 3. ffms2.dll

Place these in the Avisynth\Plugins folder wherever you just installed it.

4. AVSMeter

Put the exe in a system folder, like C:\Windows. If you know what you're doing, you can put it in any folder and add it to your path. You could also put it in whatever folder you run the batch from.

5. This batch script (below if you don't want to download it)

@echo off echo Making SCXvid keyframes... set video=%~1 set video2=%~n1 echo FFvideosource("%video%") > "%video2%_keyframes.avs" echo SCXvid("%video2%_keyframes.log") >> "%video2%_keyframes.avs" avsmeter "%video2%_keyframes.avs" del "%video2%_keyframes.avs" del "%video%.ffindex" echo Keyframes complete @pause

Paste this into a text file and rename the file to keyframes.bat (or whatever.bat you want). If you want to have all keyframes in the same folder, change the scxvid line to echo SCXvid("D:\your_path_here\%video2%_keyframes.log") >> "%video2%_keyframes.avs" You can drag your video file onto the batch script, or you can open cmd.exe in the video's folder and type: keyframes "your_video.mkv" where keyframes is the name of your batch file and the other thing is your video no matter what the extension. You don't need the quotation marks if there's no space in the filename. You'll see progress as it encodes. When done, go to "Video - Open Keyframes" in Aegisub and select the .log file. If you're in a hurry, you can load it while it's being generated, and it will load whatever's done so far. Don't forget to reload it before you time the part that wasn't done before.

SCXvid keyframe generation script v2.0 (This is supposedly faster, but it doesn't work for me, so feel free to try.)

1. Download standalone SCXvid here 2. Download FFmpeg here 3. Create batch script below:

@echo off echo Making SCXvid keyframes... set video=%~1 set video2=%~n1 -i %video% -f yuv4mpegpipe -vf scale=640:360 -pix_fmt yuv420p -vsync drop - | SCXvid.exe %video2%_keyframes.log echo Keyframes complete @pause

4. Run batch script and call your video file.

Only works with 4:2:0 video, but this shouldn't be a problem 99% of the time. Typesetting in Aegisub

• Introduction About this guide and typesetting in general

• Creating Styles Styles Manager / Styles Editor

• Typesetting Basics \an \pos \fs \bord \shad \fscx \fscy \fsp \blur \be \fad \c + timing

• Aligning Signs \frx \fry \frz \org \fax \fay

• Positioning Signs Yeah, what it says ^

• Moving Signs \move and stuff

• Layers How to create & use multiple layers

• Advanced Typesetting \clip \t & drawing/masking

• Creating Gradients Some laggy magic and stuff.

• Motion Tracking with Mocha Tracking position, scaling and rotation with mocha

• Fonts What to use when etc.

• Blending In How to make signs look like they belong there

• Using ASSDraw How to get this tricky thing called ASSDraw to work right

• Examples Examples of doing it wrong and right

• Typesetting Faster How to increase your speed with TS-heavy scripts

• Scripts Some useful scripts for Aegisub

• Writing Automation Scripts How to write your own automation scripts in lua - by lyger

• More Lua Scripting Additional tips for writing automation scripts

• Video Tutorials A few videos with real-time typesetting How to use MKV Tools

This is a simple guide for using MKV tools, as should be apparent from the title.

Download MKVtoolnix version 4.1.1, for example here. Yes, 4.1.1, because newer ones have issues and no benefits. You will see that I'm using 4.4.0, but unlike you, I know what I'm doing. I know you don't know what you're doing 'cause you're reading a guide that explains how to do what you wanna do.

Install/unpack. The 2 files that you'll use are MKVextractGUI.exe and mmg.exe.

MKVextract is for getting things out of MKVs. Open it, load an mkv file and you'll see this:

As you may guess (unless you're really dumb), you check boxes, click 'Extract,' and it extracts the streams you checked. Here you can see the subtitle stream checked (in case you couldn't tell).

That's all nice and stuff, but fuck that. MKVextract only lets you open one file at a time, so if you want to extract subs from 26 files, it'll bug the hell out of you. So instead you'll just use a search engine to locate this thing called mkvcleaver and download that.

Several minutes (hopefully seconds, but w/e) later... Under each file you have pretty much the same menu as in mkvextract. On the right is a menu for all the files you have loaded. Right now, this will extract audio and subtitles from all 6 files. If you want for example subtitles from all files and from one of them audio too, check subtitles on the right, and under the file you want audio from, check both audio and subtitles. That's all. Now you know how it works.

Let's move to mkvmerge - mmg.exe.

This is where you put MKVs together and where you can do other stuff too. Putting stuff together is easy - you just add all the streams you need and mux. Here I loaded a raw video. It has a video stream, audio stream, and chapters. Audio stream is selected, so in the lower half it shows you some properties of the audio, like language. Down under 'Output filename' you can choose where to save it. 'Start muxing' will... you can guess. Now I used the 'add' button to add two subtitle streams, as in .ass files. I unchecked the chapters because RHExcelion's chapters are useless because he's too lazy to do them right. Second subtitle stream is selected, language set to eng. You don't have to fill this shit out, but menus in players display this, so it can be useful. If you wonder why there's a second subtitle track called 'No fun allowed,' it is incidentally also RHExcelion's fault. The first of several streams of the same kind will be set as default. If you want the second one default, change it. Now you need to add fonts. OK, maybe you don't, but I'm gonna tell you how to do it anyway because this is a guide. Go to the second tab - Attachments - like you see in the picture. The upper half shows files that are already in the mkv. In this cases none, because the raw doesn't have any. The bottom half is where you add your fonts.

Go to the third tab - Global. That's where you add the real chapters, specifically where shown here. That's about all most of you will ever need, but if by any chance you ever become useful, you may wanna learn more. Under the Global tab there's also 'Splitting.' This allows you to... surprise... split mkvs. If you're wondering how they're split, it means splitting by time, like say, if some idiot leaves commercials in the encode, you'll probably wanna cut that part out, if possible. Unfortunatelly it's not always possible. The limitation is keyframes. The video can only be split at those. Anyway, you're gonna find where the part that needs to be cut out starts and ends, like the 1:30 and 2:00 in the picture. This will seek to 1:30, look for the first keyframe after that, split there, and same with 2:00. The end result will be three mkvs, the middle one being the part between 1:30 and 2:00. Make sure to get the timecodes right, ie in the format shown in the picture, as it's not gonna work otherwise. If it gets split further than you wanted, there was no keyframe between your timecode and that place. You'll have to set the timecode lower to get to the previous keyframe, but the split probably won't be where you need. As you can see, you can also split by filesize, so you can chop the file into 50MB segments or something. Now you may be wondering, since you cut out that commercial, how to put the 1st and 3rd part back together. You'll go back to the first tab, add the first part, and use the 'append' button [ie not the 'add' one] to add the third part. Then you mux, and if you're lucky, you get rid of that commercial and nothing breaks in the process.

While we're back in the first tab, let's check some of the options we have here. This image shows the video options. You can tell it is so from the video being selected. The useful things here are the framerate and aspect ratio. FPS sets framerate at which the video should play. It will not change the video if you set a different framerate. It will just play at a different speed. Only touch this if you really know what you're doing, otherwise you'll just end up screwing something up. Aspect ratio and width/height are just different ways of defining the same thing. Width/height just defines the aspect ratio, not the actual size of the video. 128x72 will do the same as 1280x720. As for the audio options, if audio/video is out of sync, you can set delay on the audio here. I believe you can set negative delay to shift it backwards, but I'm too lazy to try it right now, and since you have nothing better to do, you might as well try it yourself.

If you somehow wanted to learn how to make chapters, you can go... here. script_name="Unimportant" script_description="Import stuff, number stuff, chapter stuff, replace stuff, do other stuff to stuff." script_author="unanimated" script_url1="http://unanimated.xtreemhost.com/ts/import.lua" script_url2="https://raw.githubusercontent.com/unanimated/luaegisub/master/import.lua" script_version="2.9" clipboard=require("aegisub.clipboard") re=require'aegisub.re'

-- IMPORT/EXPORT ------function important(subs,sel,act) aline=subs[act] atext=aline.text atags=atext:match("^{(\\[^}]-)}") or "" atags=atags:gsub("\\move%b()","") atxt=atext:gsub("^{\\[^}]-}","") -- create table from user data (lyrics) sdata={} if res.mega=="update lyrics" and res.dat=="" then t_error("No lyrics given.",true) else res.dat=res.dat.."\n" for dataline in res.dat:gmatch("(.-)\n") do if dataline~="" then table.insert(sdata,dataline) end end end

-- user input sub1=res.rep1 sub2=res.rep2 sub3=res.rep3 zer=res.zeros rest=res.rest

-- this checks whether the pattern for lines with lyrics was found songcheck=0

-- paths scriptpath=ADP("?script") if script_path=="relative" then path=scriptpath.."\\"..relative_path end if script_path=="absolute" then path=absolute_path end

-- IMPORT -- if res.mega:match("import") and not res.mega:match("chptrs") then

noshift=false defect=false keeptxt=false deline=false

-- import-single-sign GUI if res.mega=="import sign" then press,reslt=ADD({ {x=0,y=0,width=1,height=1,class="label",label="File name:"}, {x=0,y=1,width=2,height=1,class="edit",name="signame"},

{x=1,y=0,width=2,height=1,class="dropdown",name="signs",items={"title","eptitle","custom","eyecatch"},value="cust om"}, {x=2,y=1,width=1,height=1,class="label",label=".ass"}, {x=0,y=2,width=3,height=1,class="checkbox",name="matchtime",label="keep current line's times",value=true,}, {x=0,y=3,width=3,height=1,class="checkbox",name="keeptext",label="keep current line's text",value=false,}, {x=0,y=4,width=3,height=1,class="checkbox",name="keeptags",label="combine tags (current overrides) ",value=false,}, {x=0,y=5,width=3,height=1,class="checkbox",name="addtags",label="combine tags (imported overrides)",value=false,}, {x=0,y=6,width=3,height=1,class="checkbox",name="noshift",label="don't shift times (import as is)",value=false,}, {x=0,y=7,width=3,height=1,class="checkbox",name="deline",label="delete original line",value=false,}, },{"OK","Cancel"},{ok='OK',close='Cancel'}) if press=="Cancel" then ak() end if reslt.signs=="custom" then signame=reslt.signame else signame=reslt.signs end noshift=reslt.noshift keeptxt=reslt.keeptext deline=reslt.deline keeptags=reslt.keeptags addtags=reslt.addtags end

-- read signs.ass if res.mega=="import signs" then file=io.open(path.."signs.ass") if file==nil then ADD({{x=0,y=0,width=1,height=1,class="label",label=path.."signs.ass\nNo such file."}},{"ok"},{cancel='ok'}) ak() end signs=file:read("*all") io.close(file) end

-- sort out if using OP, ED, signs, or whatever .ass and read the file songtype=res.mega:match("import (%a+)") if songtype=="sign" then songtype=signame end file=io.open(path..songtype..".ass") if file==nil then t_error(path..songtype..".ass\nNo such file.",true) end song=file:read("*all") io.close(file)

-- cleanup useless stuff song=song:gsub("^.-(Dialogue:)","%1") song=song.."\n" song=song:gsub("\n\n$","\n") song=song:gsub("%[[^%]]-%]\n","\n") -- make table out of lines slines={} for sline in song:gmatch("(.-)\n") do if sline~="" then table.insert(slines,sline) end end -- save (some) current line properties btext=atext basetime=aline.start_time basend=aline.end_time basestyle=aline.style baselayer=aline.layer

-- import-signs list and GUI if res.mega=="import signs" then -- make a table of signs in signs.ass signlist={} signlistxt="" for x=1,#slines do efct=slines[x]:match("%a+: %d+,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-(,[^,]-,).*") --aegisub.log("\n efct "..efct) esfct=esc(efct) if not signlistxt:match(esfct) then signlistxt=signlistxt..efct end end for sn in signlistxt:gmatch(",([^,]-),") do table.insert(signlist,sn) end -- import-signs GUI button,reslt=ADD({ {x=0,y=0,width=1,height=1,class="label",label="Choose a sign to import:"}, {x=0,y=1,width=1,height=1,class="dropdown",name="impsign",items=signlist,value=signlist[1]}, {x=0,y=2,width=1,height=1,class="checkbox",name="matchtime",label="keep current line's times",value=true,}, {x=0,y=3,width=1,height=1,class="checkbox",name="keeptext",label="keep current line's text",value=false,}, {x=0,y=4,width=1,height=1,class="checkbox",name="keeptags",label="combine tags (current overrides) ",value=false,}, {x=0,y=5,width=1,height=1,class="checkbox",name="addtags",label="combine tags (imported overrides)",value=false,}, {x=0,y=6,width=1,height=1,class="checkbox",name="noshift",label="don't shift times (import as is)",value=false,}, {x=0,y=7,width=1,height=1,class="checkbox",name="defect",label="delete 'effect'",value=false,}, {x=0,y=8,width=1,height=1,class="checkbox",name="deline",label="delete original line",value=false,}, },{"OK","Cancel"},{ok='OK',close='Cancel'}) if button=="Cancel" then ak() end if button=="OK" then whatsign=reslt.impsign end noshift=reslt.noshift defect=reslt.defect keeptxt=reslt.keeptext deline=reslt.deline keeptags=reslt.keeptags addtags=reslt.addtags -- nuke lines for the other signs for x=#slines,1,-1 do efct=slines[x]:match("%a+: %d+,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,([^,]-),.*") if efct~=whatsign then table.remove(slines,x) end end end

-- check start time of the first line (for overall shifting) starttime=slines[1]:match("%a+: %d+,([^,]+)") shiftime=string2time(starttime) if res.mega:match("sign") and noshift then shiftime=0 end

-- importing lines from whatever .ass for x=#slines,1,-1 do local ltype,layer,s_time,e_time,style,actor,margl,margr,margv,eff,txt=slines[x]:match("(%a+): (%d+),([^,]- ),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),(.*)") l2=aline if ltype=="Comment" then l2.comment=true else l2.comment=false end l2.layer=layer -- timing/shifting depending on settings if res.mega:match("import sign") and reslt.matchtime then l2.start_time=basetime l2.end_time=basend else s_time=string2time(s_time) e_time=string2time(e_time) if not noshift then s_time=s_time+basetime e_time=e_time+basetime end l2.start_time=s_time-shiftime l2.end_time=e_time-shiftime end l2.style=style l2.actor=actor l2.margin_l=margl l2.margin_r=margr l2.margin_t=margv l2.effect=eff if defect then l2.effect="" end l2.text=txt atext=txt if keeptxt and actor~="x" then btext2=btext:gsub("{\\[^}]-}","") l2.text=l2.text:gsub("^({\\[^}]-}).*","%1"..btext2) atext=btext2 end if keeptags and actor~="x" then l2.text=addtag(atags,l2.text) l2.text=l2.text:gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) :gsub("({%*?\\[^}]-})",function(tg) return extrakill(tg,2) end) end if addtags and actor~="x" then l2.text="{"..atags.."}"..l2.text l2.text=l2.text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) :gsub("({%*?\\[^}]-})",function(tg) return extrakill(tg,2) end) end

--aegisub.log("\n btext "..btext) subs.insert(act+1,l2) end -- delete line if not keeping if deline then res.keep=false end if not res.keep then subs.delete(act) else -- keep line, restore initial state + comment out atext=btext aline.comment=true aline.start_time=basetime aline.end_time=basend aline.style=basestyle aline.actor="" aline.effect="" aline.layer=baselayer aline.text=atext subs[act]=aline end end

-- EXPORT -- if res.mega=="export sign" then exportsign="" for x, i in ipairs(sel) do line=subs[i] text=line.text if x==1 then snam=line.effect end exportsign=exportsign..line.raw.."\n" end press,reslt=ADD({ {x=0,y=0,class="label",label="Target:",}, {x=0,y=1,class="label",label="Name:",}, {x=1,y=0,width=2,class="dropdown",name="addsign", items={"Add to signs.ass","Save to new file:"},value="Add to signs.ass"}, {x=1,y=1,width=2,class="edit",name="newsign",value=snam}, },{"OK","Cancel"},{ok='OK',close='Cancel'}) if press=="Cancel" then ak() end if press=="OK" then if reslt.newsign=="" then t_error("No name supplied.",true) end newsgn=reslt.newsign:gsub("%.ass$","") if reslt.addsign=="Add to signs.ass" then file=io.open(path.."signs.ass") if not file then file=io.open(path.."signs.ass","w") end sign=file:read("*all") or "" file:close() exportsign=exportsign:gsub("(%u%a+: %d+,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-),[^,]-,(.- )\n","%1,"..reslt.newsign..",%2\n") sign=sign:gsub("%u%a+: [^\n]+,"..esc(reslt.newsign)..",.-\n","") :gsub("^\n*","") sign=sign.."\n"..exportsign file=io.open(path.."signs.ass","w") file:write(sign) end if reslt.addsign=="Save to new file:" then file=io.open(path..newsgn..".ass","w") file:write(exportsign) end file:close() end end

-- IMPORT CHAPTERS if res.mega=="import chptrs" then xml=aegisub.dialog.open("Chapters file (xml)","",scriptpath.."\\","*.xml",false,true) if xml==nil then ak() end file=io.open(xml) xmlc=file:read("*all") io.close(file) chc=0 for ch in xmlc:gmatch("(.-)") do chnam=ch:match("(.-)") chtim=ch:match("(.-)") logg(chtim) chtim=chtim:gsub("(%d%d):(%d%d):(%d%d)%.(%d%d%d?)(.*)",function(a,b,c,d,e) if d:len()==2 then d=d.."0" end return d+c*1000+b*60000+a*3600000 end) l2=aline if fr2ms(1)==nil then chs=chtim else chs=fr2ms(ms2fr(chtim)) end l2.start_time=chs l2.end_time=chs+1 l2.actor="chptr" l2.text="{"..chnam.."}" subs.insert(act+chc,l2) chc=chc+1 end end

-- Update Lyrics if res.mega=="update lyrics" then sup1=esc(sub1) sup2=esc(sub2) for x, i in ipairs(sel) do progress("Updating Lyrics... "..round(x/#sel)*100 .."%") local line=subs[i] local text=subs[i].text

songlyr=sdata if line.style:match(rest) then stylecheck=1 else stylecheck=0 end if res.restr and stylecheck==0 then pass=0 else pass=1 end if res.field=="actor" then marker=line.actor elseif res.field=="effect" then marker=line.effect end denumber=marker:gsub("%d","") -- marked lines if marker:match(sup1.."%d+"..sup2) and denumber==sub1..sub2 and pass==1 then index=tonumber(marker:match(sup1.."(%d+)"..sup2)) puretext=text:gsub("{%*?\\[^}]-}","") lastag=text:match("({\\[^}]-}).$") if songlyr[index]~=nil and songlyr[index]~=puretext then text=text:gsub("^({\\[^}]-}).*","%1"..songlyr[index]) if not text:match("^{\\[^}]-}") then text=songlyr[index] end end songcheck=1 if songlyr[index]~=puretext then if lastag~=nil then text=text:gsub("(.)$",lastag.."%1") end change=" (Changed)" else change="" end aegisub.log("\nupdate: "..puretext.." --> "..songlyr[index]..change) end line.text=text subs[i]=line end end

if res.mega=="update lyrics" and songcheck==0 then press,reslt=ADD({{x=0,y=0,width=1,height=1,class="label",label="The "..res.field.." field of selected lines doesn't match given pattern \""..sub1.."#"..sub2.."\".\n(Or style pattern wasn't matched if restriction enabled.)\n#=number sequence"}},{"ok"},{cancel='ok'}) end

noshift=nil defect=nil keeptxt=nil deline=nil keeptags=nil addtags=nil end

-- NUMBERS ------function numbers(subs,sel) z=zer:len() if sub3:match("[,/;]") then startn,int=sub3:match("(%d+)[,/;](%d+)") else startn=sub3:gsub("%[.-%]","") int=1 end if sub3:match("%[") then numcycle=tonumber(sub3:match("%[(%d+)%]")) else numcycle=0 end if sub3=="" then startn=1 end startn=tonumber(startn) if startn==nil or numcycle>0 and startn>numcycle then t_error("Wrong parameters.",true) end

for i=1,#sel do line=subs[sel[i]] text=subs[sel[i]].text

if res.modzero=="number lines" then progress("Numbering... "..round(i/#sel)*100 .."%") index=i count=math.ceil(index/int)+(startn-1) if numcycle>0 and count>numcycle then repeat count=count-(numcycle-startn+1) until count<=numcycle end count=tostring(count) if z>count:len() then repeat count="0"..count until z==count:len() end number=sub1..count..sub2

if res.field=="actor" then line.actor=number end if res.field=="effect" then line.effect=number end if res.field=="layer" then line.layer=count end end

if res.modzero=="add to marker" then progress("Adding... "..round(i/#sel)*100 .."%") if res.field=="actor" then line.actor=sub1..line.actor..sub2 elseif res.field=="effect" then line.effect=sub1..line.effect..sub2 elseif res.field=="text" then text=sub1..text..sub2 end end

line.text=text subs[sel[i]]=line end end

-- CHAPTERS ------function chopters(subs,sel) if res.marker=="effect" and res.nam=="effect" then t_error("Error. Both marker and name cannot be 'effect'.",true) end if res.chmark then if res.lang~="" then kap=res.lang else kap=res.chap end for x, i in ipairs(sel) do line=subs[i] text=line.text if res.marker=="actor" then line.actor="chptr" end if res.marker=="effect" then line.effect="chptr" end if res.marker=="comment" then text=text.."{chptr}" end if res.nam=="effect" then line.effect=kap end if res.nam=="comment" then text="{"..kap.."}"..text end line.text=text subs[i]=line end else euid=2013 chptrs={} subchptrs={} if res.lang=="" then clang="eng" else clang=res.lang end for i=1, #subs do if subs[i].class == "info" then if subs[i].key=="Video File" then videoname=subs[i].value videoname=videoname:gsub("%.mkv","") end end

if subs[i].class == "dialogue" then local line=subs[i] local text=subs[i].text local actor=line.actor local effect=line.effect local start=line.start_time if text:match("{[Cc]hapter}") or text:match("{[Cc]hptr}") or text:match("{[Cc]hap}") then comment="chapter" else comment="" end if res.marker=="actor" then marker=actor:lower() end if res.marker=="effect" then marker=effect:lower() end if res.marker=="comment" then marker=comment:lower() end

if marker=="chapter" or marker=="chptr" or marker=="chap" then if res.nam=="comment" then name=text:match("^{([^}]*)}") name=name:gsub(" [Ff]irst [Ff]rame","") name=name:gsub(" [Ss]tart","") name=name:gsub("part a","Part A") name=name:gsub("part b","Part B") name=name:gsub("preview","Preview") else name=effect end

if name:match("::") then main,subname=name:match("(.+)::(.+)") sub=1 else sub=0 end

lineid=start+2013

timecode=math.floor(start/1000) tc1=math.floor(timecode/60) tc2=timecode%60 tc3=start%1000 tc4="00" if tc2==60 then tc2=0 tc1=tc1+1 end if tc1>119 then tc1=tc1-120 tc4="02" end if tc1>59 then tc1=tc1-60 tc4="01" end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end if tc3<100 then tc3="0"..tc3 end linetime=tc4..":"..tc1..":"..tc2.."."..tc3 if linetime=="00:00:00.00" then linetime="00:00:00.033" end

if sub==0 then cur_chptr={id=lineid,name=name,tim=linetime} table.insert(chptrs,cur_chptr) else cur_chptr={id=lineid,subname=subname,tim=linetime,main=main} table.insert(subchptrs,cur_chptr) end

end if line.style=="Default" then euid=euid+text:len() end end end

-- subchapters subchapters={} for c=1,#subchptrs do local ch=subchptrs[c]

ch_main=ch.main ch_uid=ch.id ch_name=ch.subname ch_time=ch.tim

schapter=" \n \n "..ch_name.."\n "..clang.."\n \n "..ch_uid.."\n "..ch_time.."\n 0\n 1\n \n"

subchapter={main=ch_main,chap=schapter} table.insert(subchapters,subchapter) end

-- chapters insert_chapters=""

if res.intro then insert_chapters=" \n "..#subs.."\n 0\n 1\n \n Intro\n "..clang.."\n \n 00:00:00.033\n \n"

end

table.sort(chptrs,function(a,b) return a.tim

for c=1,#chptrs do local ch=chptrs[c]

ch_uid=ch.id ch_name=ch.name ch_time=ch.tim

local subchaps="" for c=1,#subchapters do local subc=subchapters[c] if subc.main==ch_name then subchaps=subchaps..subc.chap end end

chapter=" \n "..ch_uid.."\n 0\n 1\n \n "..ch_name.."\n "..clang.."\n \n"..subchaps.." "..ch_time.."\n \n"

insert_chapters=insert_chapters..chapter end

chapters="\n\n\n \n 0\n 0\n "..euid.."\n"..insert_chapters.." \n"

chdialog= {{x=0,y=0,width=35,height=1,class="label",label="Text to export:"}, {x=0,y=1,width=35,height=20,class="textbox",name="copytext",value=chapters}, {x=0,y=21,width=35,height=1,class="label",label="File will be saved in the same folder as the .ass file."},}

pressed,reslt=ADD(chdialog,{"Save xml file","mp4-compatible chapters","Cancel","Copy to clipboard"},{cancel='Cancel'}) if pressed=="Copy to clipboard" then clipboard.set(chapters) end scriptpath=ADP("?script") scriptname=aegisub.file_name() scriptname=scriptname:gsub("%.ass","") if ch_script_path=="relative" then path=scriptpath.."\\"..relative_path end if ch_script_path=="absolute" then path=absolute_path end if res.sav=="script" then filename=scriptname else filename=videoname end

if pressed=="Save xml file" then local file=io.open(path.."\\"..filename..".xml","w") file:write(chapters) file:close() end if pressed=="mp4-compatible chapters" then mp4chap="" m4c=1 for ch in chapters:gmatch("(.-)") do chnam=ch:match("(.-)") chtim=ch:match("(.-)") num=tostring(m4c) if num:len()==1 then num="0"..num end chnum="CHAPTER"..num mp4chap=mp4chap..chnum.."="..chtim.."\n"..chnum.."NAME="..chnam.."\n\n" m4c=m4c+1 end chapters=mp4chap:gsub("\n\n$","") chdialog[2].value=chapters pressed,reslt=ADD(chdialog,{"Save txt file","Cancel","Copy to clipboard"},{cancel='Cancel'}) if pressed=="Copy to clipboard" then clipboard.set(chapters) end if pressed=="Save txt file" then local file=io.open(path.."\\"..filename.."chapters.txt","w") file:write(chapters) file:close() end end end end

-- STUFF ------function stuff(subs,sel) repl=0 data={} raw=res.dat.."\n" for dataline in raw:gmatch("(.-)\n") do table.insert(data,dataline) end

-- DATES GUI -- if res.stuff=="format dates" then dategui= {{x=0,y=0,class="dropdown",name="date",value="January 1st",items={"January 1","January 1st","1st of January","1st January"}}, {x=1,y=0,class="checkbox",name="log",label="log",value=false,}} pres,rez=ADD(dategui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end datelog="" end

-- MOTION BLUR GUI if res.stuff=="motion blur" then mblurgui={ {x=0,y=0,width=2,class="checkbox",name="keepblur",label="Keep current blur...",value=true}, {x=0,y=1,class="label",label="...or use blur:"}, {x=1,y=1,class="floatedit",name="mblur",value=mblur or 3},

{x=0,y=2,class="label",label="Distance:"}, {x=1,y=2,class="floatedit",name="mbdist",value=mbdist or 6},

{x=0,y=3,class="label",label="Alpha: "}, {x=1,y=3,class="dropdown",name="mbalfa",value=mbalfa or "80",items={"00","20","40","60","80","A0","C0","D0"}},

{x=0,y=4,width=2,class="checkbox",name="mb3",label="Use 3 lines instead of 2",value=mb3}, {x=0,y=5,width=2,class="label",label="Direction = first 2 points of a clip"}, } pres,rez=ADD(mblurgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end mblur=rez.mblur mbdist=rez.mbdist mbalfa=rez.mbalfa mb3=rez.mb3 end

-- EXPLODE GUI -- if res.stuff=="explode" then -- remember values if exploded then exx=expl_dist_x exy=expl_dist_y htype=ex_hor vtype=ex_ver expropx=xprop expropy=yprop exf=exfad cfad=cfade exinvx=xinv exinvy=yinv implo=implode exquence=exseq exkom=excom seqinv=invseq seqpercent=seqpc rmmbr=exremember else exx=0 exy=0 htype="all" vtype="all" expropx=false expropy=false exf=0 cfad=0 exinvx=false exinvy=false implo=false exquence=false exkom=false seqinv=false seqpercent=100 rmmbr=false end explodegui={ {x=0,y=0,class="label",label="Horizontal distance: "}, {x=1,y=0,class="floatedit",name="edistx",value=exx,hint="Maximum horizontal distance for move"}, {x=0,y=1,class="label",label="Vertical distance: "}, {x=1,y=1,class="floatedit",name="edisty",value=exy,hint="Maximum vertical distance for move"},

{x=2,y=0,class="label",label="direction: "}, {x=3,y=0,class="dropdown",name="hortype",value=htype,items={"only left","mostly left","all","mostly right","only right"}}, {x=4,y=0,class="checkbox",name="xprop",label="proportional",value=expropx,hint="Uniform move rather than random"}, {x=5,y=0,class="checkbox",name="xinv",label="inverse",value=exinvx,hint="Uniform move in the other direction"},

{x=2,y=1,class="label",label="direction: "}, {x=3,y=1,class="dropdown",name="vertype",value=vtype,items={"only up","mostly up","all","mostly down","only down"}}, {x=4,y=1,class="checkbox",name="yprop",label="proportional",value=expropy,hint="Uniform move rather than random"}, {x=5,y=1,class="checkbox",name="yinv",label="inverse",value=exinvy,hint="Uniform move in the other direction"},

{x=0,y=2,class="checkbox",name="ecfo",label="Custom fade:",hint="Default is line length",value=cfad}, {x=1,y=2,class="floatedit",name="exfad",value=exf},

{x=2,y=2,class="checkbox",name="exseq",label="sequence",value=exquence,hint="move in a sequence instead of all at the same time"}, {x=3,y=2,class="floatedit",name="seqpc",value=seqpercent,step=nil,min=1,max=100,hint="how much time should the sequence take up"}, {x=4,y=2,class="label",label="% of move"}, {x=5,y=2,class="checkbox",name="invseq",label="inverse",value=seqinv,hint="inverse sequence"},

{x=0,y=3,class="checkbox",name="impl",label="Implode",value=implo}, {x=1,y=3,class="checkbox",name="rem",label="Same for all lines",value=rmmbr,hint="use only for layered lines with the same text"}, {x=3,y=3,class="checkbox",name="excom",label="Leave original line commented out",value=exkom,width=3}, } pres,rez=ADD(explodegui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end expl_dist_x=rez.edistx expl_dist_y=rez.edisty exfad=rez.exfad cfade=rez.ecfo ex_hor=rez.hortype ex_ver=rez.vertype xprop=rez.xprop yprop=rez.yprop xinv=rez.xinv yinv=rez.yinv implode=rez.impl exseq=rez.exseq seqpc=round(rez.seqpc) invseq=rez.invseq exremember=rez.rem excom=rez.excom exploded=true end

-- Randomized Transforms GUI -- if res.stuff=="randomized transforms" then rine=subs[sel[1]] durone=rine.end_time-rine.start_time rtgui={ {x=0,y=0,class="checkbox",name="rtfad",label="Random Fade",width=2,value=true}, {x=0,y=1,class="checkbox",name="rtdur",label="Random Duration",width=2,value=true}, {x=2,y=0,class="label",label="Min: "}, {x=2,y=1,class="label",label="Max: "}, {x=3,y=0,class="floatedit",name="minfad",width=1,value=math.floor(durone/5),min=0}, {x=3,y=1,class="floatedit",name="maxfad",width=1,value=durone}, {x=4,y=0,class="checkbox",name="rtin",label="Fade In",width=3,value=false,hint="In instead of Out"}, {x=4,y=1,class="checkbox",name="maxisdur",label="Max = Current Duration",width=4,value=true,hint="The maximum will be the duration of each selected line"},

{x=0,y=3,class="label",label="Transform"}, {x=1,y=3,class="dropdown",name="rttag",value="blur", items={"blur","bord","shad","fs","fsp","fscx","fscy","fax","fay","frz","frx","fry","xbord","ybord","xshad","yshad"}}, {x=2,y=3,class="label",label="Min: "}, {x=4,y=3,class="label",label=" Max: "}, {x=3,y=3,class="floatedit",name="mintfn",width=1,value=0,hint="Minimum value for a given tag"}, {x=5,y=3,class="floatedit",name="maxtfn",width=3,value=0,hint="Maximum value for a given tag"},

{x=0,y=4,class="label",width=2,label="Colour Transform"}, {x=2,y=4,class="label",label="Max: "}, {x=3,y=4,class="floatedit",name="rtmaxc",value=100,min=1,max=100,hint="Maximum % of colour change.\nColour tag must be present. Otherwise set to 100%."}, {x=4,y=4,class="label",label="%"}, {x=5,y=4,class="checkbox",name="rtc1",label="\\c ",value=true}, {x=6,y=4,class="checkbox",name="rtc3",label="\\3c ",value=false}, {x=7,y=4,class="checkbox",name="rtc4",label="\\4c ",value=false},

{x=0,y=5,class="checkbox",name="rtacc",label="Use Acceleration",width=2,value=false}, {x=2,y=5,class="label",label="Min: "}, {x=3,y=5,class="floatedit",name="minacc",width=1,value=1,min=0}, {x=4,y=5,class="label",label=" Max:"}, {x=5,y=5,class="floatedit",name="maxacc",width=3,value=1,min=0},

{x=0,y=6,class="checkbox",name="rtmx",label="Random Move X",width=2,value=false}, {x=2,y=6,class="label",label="Min: "}, {x=3,y=6,class="floatedit",name="minmx",width=1,value=0}, {x=4,y=6,class="label",label=" Max:"}, {x=5,y=6,class="floatedit",name="maxmx",width=3,value=0},

{x=0,y=7,class="checkbox",name="rtmy",label="Random Move Y",width=2,value=false}, {x=2,y=7,class="label",label="Min: "}, {x=3,y=7,class="floatedit",name="minmy",width=1,value=0}, {x=4,y=7,class="label",label=" Max:"}, {x=5,y=7,class="floatedit",name="maxmy",width=3,value=0}, } if rtremember then for key,val in ipairs(rtgui) do if val.class~="label" then val.value=rez[val.name] end end end rtchoice={"Fade/Duration","Number Transform","Colour Transform","Help","Cancel"} rthlp={"Fade/Duration","Number Transform","Colour Transform","Cancel"} pres,rez=ADD(rtgui,rtchoice,{ok='Fade/Duration',close='Cancel'}) if pres=="Help" then rthelp={x=0,y=8,width=8,height=4,class="textbox",value="This is supposed to be used after 'split into letters' or with gradients.\n\nFade/Duration Example: Min: 500, Max: 2000.\nA random number between those is generated for each line, let's say 850.\nThis line's duration will be 850ms, and it will have a 850ms fade out.\n\nNumber Transform Example: Blur, Min: 0.6, Max: 2.5\nRandom number generated: 1.7. Line will have: \\t(\\blur1.7)\n\nRandom Colour Transform creates transforms to random colours. \nMax % transform limits how much the colour can change.\n\nAccel works with either transform function.\n\nRandom Move works as an additional option with any function.\nIt can be used on its own if you uncheck other stuff. Works with Fade In."} table.insert(rtgui,rthelp) pres,rez=ADD(rtgui,rthlp,{ok='Fade/Duration',close='Cancel'}) end if pres=="Cancel" then ak() end if pres=="Fade/Duration" then RTM="FD" end if pres=="Number Transform" then RTM="NT" end if pres=="Colour Transform" then RTM="CT" end rtremember=true RTF=rez.rtfad RTD=rez.rtdur MnF=rez.minfad MxF=rez.maxfad RTin=rez.rtin RTMax=rez.maxisdur RTT=rez.rttag MnT=rez.mintfn MxT=rez.maxtfn RTA=rez.rtacc MnA=rez.minacc MxA=rez.maxacc MnX=rez.minmx MxX=rez.maxmx MnY=rez.minmy MxY=rez.maxmy MxC=round(rez.rtmaxc*255/100) rtcol={} if rez.rtc1 then table.insert(rtcol,1) end if rez.rtc3 then table.insert(rtcol,3) end if rez.rtc4 then table.insert(rtcol,4) end end

-- Clone Clip GUI -- if res.stuff=="clone clip" then if clone_h then cchc=clone_h else cchc=2 end if clone_v then ccvc=clone_v else ccvc=2 end if dist_h then cchd=dist_h else cchd=20 end if dist_v then ccvd=dist_v else ccvd=20 end if ccshift then ccsh=ccshift else ccsh=0 end ccgui={ {x=0,y=0,class="label",label="Horizontal distance: "}, {x=1,y=0,class="intedit",name="hdist",value=cchd,min=1}, {x=0,y=1,class="label",label="Horizontal clones: "}, {x=1,y=1,class="intedit",name="hclone",value=cchc,min=1}, {x=0,y=2,class="label",label="Vertical distance: "}, {x=1,y=2,class="intedit",name="vdist",value=ccvd,min=1}, {x=0,y=3,class="label",label="Vertical clones: "}, {x=1,y=3,class="intedit",name="vclone",value=ccvc,min=1}, {x=0,y=4,class="label",label="Shift even rows by:"}, {x=1,y=4,class="intedit",name="ccshift",value=ccsh,min=0}, } pres,rez=ADD(ccgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end clone_h=rez.hclone dist_h=rez.hdist clone_v=rez.vclone dist_v=rez.vdist ccshift=rez.ccshift end

-- DISSOLVE GUI ------if res.stuff=="dissolve text" then if dlast then ddistance=v_dist ddlines=dlines dshape=shape dalter=alternate dissin=disin otherd=otherdis v2direction=v2d else ddistance=10 ddlines=10 dshape="square" dalter=true dissin=false otherd=false v2direction="randomly" end dissgui={ {x=0,y=0,class="label",label="Distance between points: "}, {x=1,y=0,class="floatedit",name="ddist",value=ddistance,min=4,step=2}, {x=0,y=1,class="label",label="Shape of clips:"}, {x=1,y=1,class="dropdown",name="shape",items={"square","square 2","diamond","triangle 1","triangle 2","hexagon","wave/hexagram","vertical lines","horizontal lines"},value=dshape}, {x=0,y=2,class="checkbox",name="alt",label="Shift even rows (all except vertical/horizontal lines)",value=dalter,width=2}, {x=0,y=3,class="checkbox",name="disin",label="Reverse effect (fade in rather than out)",value=dissin,width=2}, {x=0,y=4,class="checkbox",name="otherdiss",label="Dissolve v2. ... Lines:",value=otherd,hint="only square, diamond, vertical lines"}, {x=1,y=4,class="floatedit",name="modlines",value=ddlines,min=6,step=2}, {x=0,y=5,class="label",label=" Dissolve v2: Dissolve"}, {x=1,y=5,class="dropdown",name="v2dir",items={"randomly","from top","from bottom","from left","from right"},value=v2direction}, } pres,rez=ADD(dissgui,{"OK","What Is This","Cancel"},{ok='OK',close='Cancel'}) if pres=="What Is This" then dishelp={x=0,y=6,width=10,height=8,class="textbox",value="The script can either automatically draw a clip around the text,\nor you can make your own clip.\nThe automation only considers position, alignment, and scaling,\nso for anything more complex, make your own.\nYou can just try it without a clip,\nand if the result isn't right, draw a clip first. (Only 4 points!)\n\n'Distance between points' will be the distance between the\nstarting points of all the little iclips.\nLess Distance = more clips = more lag,\nso use the lowest values only for smaller text.\nYou can run this on one line or fbf lines.\nThe ideal 'fade' is as many frames as the given Distance.\nThat way the clips grow by 1 pixel per frame.\nAny other way doesn't look too good,\nbut you can apply Distance 10 over 20 lines\nand have each 2 consecutive lines identical.\nMore Distance than lines doesn't look so bad, and the effect is 'faster'.\nIf you apply this to 1 line, the line will be split to have the effect applied to as many frames as the Distance is. (This is preferred.)\nFor hexagon, the actual distance is twice the input. (It grows faster.)\n\nThe shapes should be self-explanatory, so just experiment.\n\n'Shift even rows' means that even rows will have an offset\nfrom odd rows by half of the given Distance.\nNot checking this will have a slightly different and less regular effect,\nthough it also depends on the shape. Again, experiment.\n\nIf you need to apply this to several layers, you have to do it one by one. The GUI remembers last values. But more layers = more lag.\n\nAll kinds of things can make this lag, so use carefully.\nLines are less laggy than other shapes.\nHorizontal lines are the least laggy. (Unless you have vertical text.)\n\nFor longer fades, use more Distance.\nThis works great with vertical lines but is pretty useless with horizontal.\n\n'Reverse effect' is like fade in while the default is fade out.\nWith one line selected, it applies to the first frames.\n\n'Dissolve v2' is a different kind of dissolve\nand only works with square, diamond, and vertical lines.\nLine count for this is independent on distance between points.\nIt's the only effect that allows Distance 4.\n'Shift even rows' has no effect here.\n\nYou can set a direction of Dissolve v2.\nObviously top and bottom is nonsense for vertical lines.\n'Reverse effect' reverses the direction too, so choose the opposite.\n\nThere may be weird results with some combinations of settings.\nThere may be some malfunctions, as the script is pretty complex.\nSome of them -might- be fixed by reloading automation scripts.\nMakes no sense with \\move. Nukes \\fad.\n\nThere are some fun side effects.\nFor example with 'square 2' and 'Shift even rows',\nyou get a brick wall on the last frame."} table.insert(dissgui,dishelp) pres,rez=ADD(dissgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) end if pres=="Cancel" then ak() end dlast=true v_dist=rez.ddist shape=rez.shape alternate=rez.alt disin=rez.disin otherdis=rez.otherdiss dlines=rez.modlines v2d=rez.v2dir dis2=false if v2d=="randomly" then dir=5 end if v2d=="from top" then dir=8 end if v2d=="from bottom" then dir=2 end if v2d=="from left" then dir=4 end if v2d=="from right" then dir=6 end if not otherdis and v_dist==4 then t_error("Distance 4 is only allowed for square mod. Changing to 6.") v_dist=6 end if otherdis then if shape=="square" or shape=="diamond" or shape=="vertical lines" then dis2=true else dis2=false end if shape=="square" then alternate=false end if shape=="diamond" then alternate=true end end if dis2 and #sel==1 then linez=dlines elseif dis2 and #sel>1 then linez=#sel else linez=v_dist end

-- DISSOLVE create lines if only one selected ------if #sel==1 then rine=subs[sel[1]] rine.text=rine.text:gsub("\\fad%(.-%)","") start=rine.start_time endt=rine.end_time startf=ms2fr(start) endf=ms2fr(endt) lframes=ms2fr(endt-start) if lframesb end) end

-- DISSOLVE Initial Calculations ------line=subs[sel[1]] text=line.text text=text:gsub("\\clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) a=math.floor(a) b=math.floor(b) c=math.ceil(c) d=math.ceil(d) return string.format("\\clip(m %d %d l %d %d %d %d %d %d)",a,b,c,b,c,d,a,d) end) -- draw clip when no clip present if not text:match("\\clip") then styleref=stylechk(subs,line.style) vis=text:gsub("{[^}]-}","") width,height,descent,ext_lead=aegisub.text_extents(styleref,vis) bord=text:match("\\bord([%d%.]+)") if bord==nil then bord=styleref.outline end bord=math.ceil(bord) scx=text:match("\\fscx([%d%.]+)") if scx==nil then scx=styleref.scale_x end scx=scx/100 scy=text:match("\\fscy([%d%.]+)") if scy==nil then scy=styleref.scale_y end scy=scy/100 wi=round(width) he=round(height) text2=getpos(subs,text) if not text:match("\\pos") then text=text2 end xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if h_al=="left" then cx1=xx cx2=xx+wi*scx end if h_al=="right" then cx1=xx-wi*scx cx2=xx end if h_al=="mid" then cx1=xx-wi/2*scx cx2=xx+wi/2*scx end if v_al=="top" then cy1=yy cy2=yy+he*scy end if v_al=="bottom" then cy1=yy-he*scy cy2=yy end if v_al=="mid" then cy1=yy-he/2*scy cy2=yy+he/2*scy end cx1=math.floor(cx1-bord) cx2=math.ceil(cx2+bord) cy1=math.floor(cy1-bord) cy2=math.ceil(cy2+bord) text=addtag("\\clip(m "..cx1.." "..cy1.." l "..cx2.." "..cy1.." "..cx2.." "..cy2.." "..cx1.." "..cy2..")",text) end -- get outermost clip points even if it's irregular (though it should be fucking regular) exes={} wais={} klip=text:match("\\clip%(m [%d%-]+ [%d%-]+ l [%d%-]+ [%d%-]+ [%d%-]+ [%d%-]+ [%d%-]+ [%d%-]+") for ex,wai in klip:gmatch("([%d%-]+) ([%d%-]+)") do table.insert(exes,tonumber(ex)) table.insert(wais,tonumber(wai)) end table.sort(exes) table.sort(wais) x1=exes[1]-2 x2=exes[4]+2 y1=wais[1]-2 y2=wais[4]+2 width=x2-x1 height=y2-y1 h_dist=2*v_dist if shape=="hexagon" then h_dist=math.floor(h_dist*2) end rows=math.ceil(height/v_dist) rows2=math.ceil(height/v_dist/2) points=math.ceil(width/h_dist)+1 if shape=="horizontal lines" then vert=2*v_dist rows=math.ceil(rows/2)+1 else vert=v_dist end if shape:match("triangle") or shape=="wave/hexagram" then rows=rows+1 end

xpoints={} for w=1,points do point=x1+h_dist*(w-1) table.insert(xpoints,point) end ypoints={} for w=1,rows do point=y1+vert*(w-1) table.insert(ypoints,point) end ypoints2={} for w=1,rows2 do point=y1+2*v_dist*(w-1) table.insert(ypoints2,point) end

-- this is all centers of individual iclip shapes allpoints1={} for w=1,#ypoints do for z=1,#xpoints do u=0 if alternate and w%2==0 then u=h_dist/2 else u=0 end -- every even row is shifted by half of h_dist from odd rows rnum=math.random(2000,6000) if dir==5 then rindex=rnum end if dir==8 then rindex=rnum*ypoints[w]^2 end if dir==4 then rindex=rnum*xpoints[z]^2 end if dir==2 then rindex=0-rnum*ypoints[w]^2 end if dir==6 then rindex=0-rnum*xpoints[z]^2 end point={xpoints[z]+u,ypoints[w],rindex} table.insert(allpoints1,point) end end

allpoints2={} for w=1,#ypoints2 do for z=1,#xpoints do u=0 if alternate and w%2==0 then u=h_dist/2 else u=0 end -- every even row is shifted by half of h_dist from odd rows rnum=math.random(2000,6000) if dir==5 then rindex=rnum end if dir==8 then rindex=rnum*ypoints2[w]^2 end if dir==4 then rindex=rnum*xpoints[z]^2 end if dir==2 then rindex=0-rnum*ypoints2[w]^2 end if dir==6 then rindex=0-rnum*xpoints[z]^2 end point={xpoints[z]+u,ypoints2[w],rindex} table.insert(allpoints2,point) end end

if dis2 and shape=="square" or shape=="square 2" or shape=="hexagon" then allpoints=allpoints2 else allpoints=allpoints1 end if dis2 and shape=="vertical lines" then allpoints={} for w=1,#xpoints do rnum=math.random(2000,6000) if dir==4 then rindex=rnum*xpoints[w]^2 elseif dir==6 then rindex=0-rnum*xpoints[w]^2 else rindex=rnum end table.insert(allpoints,{xpoints[w],0,rindex}) end end if dis2 then table.sort(allpoints,function(a,b) return a[3]

-- DISSOLVE v2 Calculations ------if dis2 then d2c=0 fullclip="" dis2tab={} rnd=1 ppl=#allpoints/linez for w=1,#allpoints do pt=allpoints[w] vd=v_dist

if shape=="square" then krip="m "..pt[1]-vd.." "..pt[2]-vd.." l "..pt[1]+vd.." "..pt[2]-vd.." "..pt[1]+vd.." "..pt[2]+vd.." "..pt[1]-vd.." "..pt[2]+vd.." " end

if shape=="diamond" then krip="m "..pt[1].." "..pt[2]-vd.." l "..pt[1]+vd.." "..pt[2].." "..pt[1].." "..pt[2]+vd.." "..pt[1]-vd.." "..pt[2].." " end

if shape=="vertical lines" then krip="m "..pt[1]-v_dist.." "..y1.." l "..pt[1]+v_dist.." "..y1.." "..pt[1]+v_dist.." "..y2.." "..pt[1]-v_dist.." "..y2.." " end

fullclip=fullclip..krip d2c=d2c+1 if d2c>=math.floor(ppl) and w>=ppl*rnd then d2c=0 rnd=rnd+1 table.insert(dis2tab,fullclip) end end end -- DISSOLVE END ------end

-- What is the Matrix -- if res.stuff=="what is the Matrix?" then matrixgui={ {x=0,y=0,class="label",label="Max. transformations per letter: "}, {x=1,y=0,class="intedit",name="tpl",value=4,min=2}, {x=0,y=1,class="label",label="Frames to stay the same: "}, {x=1,y=1,class="intedit",name="fts",value=2,min=1}, {x=0,y=2,class="label",label="Character set: "},

{x=1,y=2,class="dropdown",name="charset",items={"UPPERCASE","lowercase","Both","More","Everything"},value ="Both"}, {x=0,y=3,class="label",label="Chance to keep letter (0-10):"}, {x=1,y=3,class="intedit",name="mkeep",value=5,min=0,max=10}, {x=0,y=4,width=2,class="checkbox",name="showall",label="Show all letters from the start",value=true,}, {x=0,y=5,width=2,class="label",label="Monospace fonts are optimal. For others, use left alignment."}, } if matrixres then for key,val in ipairs(matrixgui) do if val.class=="checkbox" or val.class=="dropdown" or val.class=="intedit" then val.value=rez[val.name] end end end pres,rez=ADD(matrixgui,{"What Is the Matrix?","Cancel"},{ok='What Is the Matrix?',close='Cancel'}) if pres=="Cancel" then ak() end AB="ABCDEFGHIJKLMNOPQRSTUVWXYZ" ab="abcdefghijklmnopqrstuvwxyz" Ab="!?()$&+-=" aB="@#%^*/[]';:,.|" if rez.charset=="UPPERCASE" then chset=AB.." " elseif rez.charset=="lowercase" then chset=ab.." " elseif rez.charset=="Both" then chset=AB..ab.." " elseif rez.charset=="More" then chset=AB..ab..Ab.." " else chset=AB..ab..Ab..aB.." " end ABC=chset:len() mframes=rez.tpl fpl=rez.fts matrixres=rez end

if res.stuff=="time by frames" then frs=res.rep1:match("%-?%d+") or 0 fre=res.rep2:match("%-?%d+") or 0 rine=subs[sel[1]] fstart=ms2fr(rine.start_time) fendt=ms2fr(rine.end_time) rine.start_time=fr2ms(fstart) rine.end_time=fr2ms(fendt) subs[sel[1]]=rine if frs==0 and fre==0 then t_error("Use Left/Right to input \nnumber of frames \nto shift by for start/end.",true) end end

if res.stuff=="duplicate and shift lines" then FB=tonumber(res.rep1:match("^%d+")) or 0 FA=tonumber(res.rep2:match("^%d+")) or 0 if FA+FB==0 then t_error("Use the Left and Right fields to set how many times you want to duplicate the line.",true) end FALL=FA+FB+1 FB1=FB+1 end

KO1=subs[sel[1]].start_time

if res.stuff:match("replacer") or res.stuff=="fix kara tags for fbf lines" then table.sort(sel,function(a,b) return a>b end) end -- LINES START HERE ------for i=#sel,1,-1 do line=subs[sel[i]] text=line.text style=line.style

-- What is the Matrix -- if res.stuff=="what is the Matrix?" then start=line.start_time endt=line.end_time startf=ms2fr(start) tags=text:match("^{\\[^}]-}") or "" visible=text:gsub("{[^}]-}","") ltrs={} ltrs2={} matches=re.find(visible,".") for l=1,#matches do table.insert(ltrs,matches[l].str) r=math.random(1,ABC) table.insert(ltrs2,chset:sub(r,r)) end base="" lines={} for l=1,#ltrs do for f=1,mframes do if f1 then letter=lastletter end if l==1 and letter==" " then letter="e" end if l==#ltrs and letter==" " then letter="s" end txt=base..letter lastletter=letter else letter=ltrs[l] txt=base..letter base=txt end if not rez.showall then txt=txt.."{\\alpha&HFF&}" end for n=l+1,#ltrs do if rez.showall then y=math.random(1,ABC) letter=chset:sub(y,y) z=math.random(0,9) if zl then letter=ltrs2[n] end ltrs2[n]=letter if n==#ltrs and letter==" " then letter="k" end txt=txt..letter else txt=txt..ltrs[n] end end txt=tags..txt fact=l*mframes+f-mframes-1 startfr=startf+fact*fpl st=fr2ms(startfr) et=fr2ms(startfr+fpl) l2={txt,st,et} table.insert(lines,l2) end lastletter=nil end for ln=#lines,1,-1 do lin=lines[ln] line.text=lin[1] line.start_time=lin[2] line.end_time=lin[3] if ln==#lines and endt>lin[3] then line.end_time=endt end subs.insert(sel[i]+1,line) end line.comment=true end

if res.stuff=="save/load" and i==1 then if savedata==nil then savedata="" end if res.dat~="" then savedata=savedata.."\n\n"..res.dat savedata=savedata :gsub("^\n\n","") :gsub("\n\n\n","\n\n") ADD({{class="label",label="Data saved.",x=0,y=0,width=20,height=2}},{"OK"},{close='OK'}) else

ADD({{x=0,y=0,width=50,height=18,class="textbox",name="savetxt",value=savedata},},{"OK"},{close='OK'}) end end

if res.stuff=="replacer" then lim=sub3:match("^%d+") if lim==nil then limit=1 else limit=tonumber(lim) end replicant1=sub1:gsub("\\","\\"):gsub("\\\\","\\") replicant2=sub2:gsub("\\","\\"):gsub("\\\\","\\") tk=text count=0 if res.regex=="lua patterns" then repeat text=text:gsub(replicant1,replicant2) count=count+1 until count==limit if text~=tk then repl=repl+1 if res.log then r1=replicant1:gsub("%%%(","_L_"):gsub("%%%)","_R_"):gsub("%(",""):gsub("%)",""):gsub("_L_","%%%("):gsub("_ R_","%%%)") for l1 in tk:gmatch(r1) do aegisub.log("\nOrig: "..l1) l2=l1:gsub(replicant1,replicant2) aegisub.log("\nMod: "..l2) end end end else repeat text=re.sub(text,replicant1,replicant2) count=count+1 until count==limit if text~=tk then repl=repl+1 if res.log then for r1 in re.gfind(tk,replicant1) do aegisub.log("\nOrig: "..r1) r2=re.sub(r1,replicant1,replicant2) aegisub.log("\nMod: "..r2) end end end

end end if res.stuff=="lua calc" then lim=sub3:match("^%d+") if lim==nil then limit=1 else limit=tonumber(lim) end replicant1=sub1:gsub("\\","\\") replicant2=sub2:gsub("\\","\\") replicant1=sub1:gsub("\\\\","\\") replicant2=sub2:gsub("\\\\","\\") replicant2="||"..replicant2.."||" replicant2=replicant2:gsub("%.%.","||") tk=text count=0 repeat text=text:gsub(replicant1,function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) tab1={"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"} tab2={a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p} r2=replicant2 asd=1 repeat for r=1,16 do r2=r2 :gsub(tab1[r].."%*([%d%.]+)",function(num) return tab2[r]*tonumber(num) end) :gsub(tab1[r].."%/([%d%.]+)",function(num) return tab2[r]/tonumber(num) end) :gsub(tab1[r].."%+([%d%.]+)",function(num) return tab2[r]+tonumber(num) end) :gsub(tab1[r].."%-([%d%.]+)",function(num) return tab2[r]-tonumber(num) end) :gsub("([%d%.]+)%*([%d%.]+)",function(n1,n2) return tonumber(n1)*tonumber(n2) end) :gsub("([%d%.]+)%/([%d%.]+)",function(n1,n2) return tonumber(n1)/tonumber(n2) end) :gsub("([%d%.]+)%+([%d%.]+)",function(n1,n2) return tonumber(n1)+tonumber(n2) end) :gsub("([%d%.]+)%-([%d%.]+)",function(n1,n2) return tonumber(n1)-tonumber(n2) end) end for r=1,16 do if tab2[r]~=nil then r2=r2:gsub("([|%*%/%+%-])"..tab1[r].."|","%1"..tab2[r].."|") r2=r2:gsub("%("..tab1[r].."%)","("..tab2[r]..")") end end r2=r2:gsub("round%(([^%)]+)%)",function(num) return math.floor(tonumber(num)+0.5) end) asd=asd+1 until not r2:match("[%*%/%+%-]") or asd==12 r2=r2:gsub("||","") return r2 end) count=count+1 until count==limit if text~=tk then repl=repl+1 end end if res.stuff=="add comment" then text=text.."{"..res.dat.."}" end if res.stuff=="add comment line by line" then kom=data[i] if kom then text=text.."{"..kom.."}" end end if res.stuff=="make comments visible" then text=text:gsub("{([^\\}]-)}","%1") end if res.stuff=="switch commented/visible" then text=text :gsub("\\N","_br_") :gsub("{([^\\}]-)}","}%1{") :gsub("^([^{]+)","{%1") :gsub("([^}]+)$","%1}") :gsub("([^}])({\\[^}]-})([^{])","%1}%2{%3") :gsub("^({\\[^}]-})([^{])","%1{%2") :gsub("([^}])({\\[^}]-})$","%1}%2") :gsub("{}","") :gsub("_br_","\\N") end if res.stuff=="reverse text" then text=(text:match("^{\\[^}]-}") or "")..text.reverse(text:gsub("{[^}]-}","")) end if res.stuff=="reverse words" then tags=(text:match("^{\\[^}]-}") or "") text=text:gsub("{[^}]-}","") nt="" for l in text:gmatch("[^%s]+") do nt=" "..l..nt end nt=nt:gsub("^ ","") text=tags..nt end

-- MOTION BLUR ------if res.stuff=="motion blur" then if text:match("\\clip%(m") then if not text:match("\\pos") then text=getpos(subs,text) end if not rez.keepblur then text=addtag("\\blur"..mblur,text) end text=text:gsub("{%*?\\[^}]-}",function(tg) return duplikill(tg) end) c1,c2,c3,c4=text:match("\\clip%(m ([%-%d%.]+) ([%-%d%.]+) l ([%-%d%.]+) ([%-%d%.]+)") if c1==nil then t_error("There seems to be something wrong with your clip",true) end text=text:gsub("\\clip%b()","") text=addtag("\\alpha&H"..mbalfa.."&",text) cx=c3-c1 cy=c4-c2 cdist=math.sqrt(cx^2+cy^2) mbratio=cdist/mbdist*2 mbx=round(cx/mbratio*100)/100 mby=round(cy/mbratio*100)/100 text2=text:gsub("\\pos%(([%-%d%.]+),([%-%d%.]+)",function(a,b) return "\\pos("..a-mbx..","..b-mby end) l2=line l2.text=text2 subs.insert(sel[i]+1,l2) table.insert(sel,sel[#sel]+1) if rez.mb3 then line.text=text subs.insert(sel[i]+1,line) table.insert(sel,sel[#sel]+1) end text=text:gsub("\\pos%(([%-%d%.]+),([%-%d%.]+)",function(a,b) return "\\pos("..a+mbx..","..b+mby end) else noclip=true end end

-- REVERSE TRANSFORMS ------if res.stuff=="reverse transforms" then styleref=stylechk(subs,line.style) text=text:gsub("\\1c","\\c") tags=(text:match("^{\\[^}]-}") or "") text=text:gsub("^{[^}]-}","") tags=cleantr(tags) tags=duplikill(tags) tags=tags:gsub("\\fs(%d)","\\fsize%1") notrans=tags:gsub("\\t%b()","") for tr in tags:gmatch("\\t%b()") do tr=tr:gsub("\\i?clip%([^%)]+%)","") :gsub("\\t%(","") :gsub("%)$","") for tag in tr:gmatch("\\[1234]?%a+") do if not notrans:match(tag) then tags=fill_in(tags,tag) end tags=tags:gsub(tag.."([^\\}]+)([^}]-)"..tag.."([^\\}%)]+)",tag.."%3%2"..tag.."%1") end end tags=tags:gsub("(i?clip%([^%)]+%))([^}]-\\t[^}]-)(i?clip%([^%)]+%))","%3%2%1") tags=tags:gsub("\\fsize","\\fs") text=tags..text end if res.stuff=="fake capitals" then tags=(text:match("^{\\[^}]-}") or "") text=text:gsub("^{\\[^}]-}","") :gsub("(%u)","{\\fs"..sub1.."}%1{\\fs}") :gsub("{\\fs}(%p?){\\fs%d+}","{\\fs}%1") repeat text=text:gsub("{\\fs}([%w']+){\\fs}","{\\fs}%1") until not text:match("{\\fs}([%w']+){\\fs}") text=tags..text text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") end

if res.stuff=="format dates" then text2=text:gsub("{[^}]-}","") if rez.date=="January 1" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)(st|nd|th)","\\1 \\2") text=re.sub(text,"(\\d+)(st|nd|th|st of|nd of|th of) (January|February|March|April|May|June|July|August|September|October|November|December)","\\3 \\1") end if rez.date=="January 1st" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)","\\1 \\2th") text=re.sub(text,"(\\d+)(st|nd|th|st of|nd of|th of) (January|February|March|April|May|June|July|August|September|October|November|December)","\\3 \\1th") text=text:gsub("(%d)thth","%1th") :gsub("1thst","1st") :gsub("2thnd","2nd") :gsub("1th","1st") :gsub("2th","2nd") end if rez.date=="1st of January" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)(st|nd|th)?","\\2\\3 of \\1") text=re.sub(text,"(\\d+) of (January|February|March|April|May|June|July|August|September|October|November|December)","\\1th of \\2") text=text:gsub("(%d)thth","%1th") :gsub("1thst","1st") :gsub("2thnd","2nd") :gsub("1th","1st") :gsub("2th","2nd") text=re.sub(text,"(\\d+)(st|nd|th) (January|February|March|April|May|June|July|August|September|October|November|December)","\\1\\2 of \\3") end if rez.date=="1st January" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)(st|nd|th)?","\\2\\3 \\1") text=re.sub(text,"(\\d+) (January|February|March|April|May|June|July|August|September|October|November|December)","\\1th \\2") text=text:gsub("(%d)thth","%1th") :gsub("1thst","1st") :gsub("2thnd","2nd") :gsub("1th","1st") :gsub("2th","2nd") text=re.sub(text,"(\\d+)(st|nd|th) of (January|February|March|April|May|June|July|August|September|October|November|December)","\\1\\2 \\3") end textn=text:gsub("{[^}]-}","") if text2~=textn then datelog=text2.." -> "..textn.."\n"..datelog end end

if res.stuff=="transform \\k to \\t\\alpha" then repeat text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until not text:match("{(\\[^}]-)}{(\\[^}]-)}") if text:match("^{[^}]-\\alpha") then alf=text:match("^{[^}]-\\alpha&H(%x%x)&") else alf="00" end text=text:gsub("\\alpha&H%x%x&","") tab={} for kpart in text:gmatch("{[^}]-\\k[fo][%d%.]+[^}]-}[^{]*") do table.insert(tab,kpart) end lastim=0 text="" for k=1,#tab do part=tab[k] tim=tonumber(part:match("\\k[fo]([%d%.]+)"))*10 part=part:gsub("\\k[fo][%d%.]+","\\alpha&HFF&\\t("..lastim..","..lastim+tim..",\\alpha&H"..alf.."&)") tab[k]=part lastim=lastim+tim text=text..tab[k] end end

-- SPLIT and EXPLODE ------if res.stuff=="split into letters" or res.stuff=="explode" then l2=line tags=(text:match("^{\\[^}]-}") or "") vis=text:gsub("{[^}]-}","") af="{\\alpha&HFF&}" a0="{\\alpha&H00&}" letters={} ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(letters,ltrmatches[l].str) end if savetab==nil then savetab={} end -- create texts for all resulting lines for l=#letters,1,-1 do tx=af ltr=a0..letters[l]..af for t=1,#letters do ltr2=letters[t] if t==l then ltr2=ltr end tx=tx..ltr2 end tx=textmod(text,tx) txt2=tags..tx if not txt2:match("\\pos") then txt2=getpos(subs,txt2) end txt2=txt2:gsub("{\\alpha&HFF&}$","") txt2=txt2:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") txt2=txt2:gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) -- Explode if res.stuff=="explode" then dur=line.end_time-line.start_time if cfade then FO=exfad else FO=dur end if implode then expfad="\\fad("..FO..",0)" else expfad="\\fad(0,"..FO..")" end if FO==0 then EFO="" else EFO=expfad end if ex_hor=="all" then ex1a=0-expl_dist_x ex1b=expl_dist_x end if ex_hor=="only left" then ex1a=0-expl_dist_x ex1b=0 end if ex_hor=="only right" then ex1a=0 ex1b=expl_dist_x end if ex_hor=="mostly left" then ex1a=0-expl_dist_x ex1b=expl_dist_x/3 end if ex_hor=="mostly right" then ex1a=0-expl_dist_x/3 ex1b=expl_dist_x end if ex_ver=="all" then ex2a=0-expl_dist_y ex2b=expl_dist_y end if ex_ver=="only up" then ex2a=0-expl_dist_y ex2b=0 end if ex_ver=="only down" then ex2a=0 ex2b=expl_dist_y end if ex_ver=="mostly up" then ex2a=0-expl_dist_y ex2b=expl_dist_y/3 end if ex_ver=="mostly down" then ex2a=0-expl_dist_y/3 ex2b=expl_dist_y end rvrs=#letters-l+1 if xinv then xind=rvrs else xind=l end if yinv then yind=rvrs else yind=l end if invseq then seqt=rvrs else seqt=l end if implode then seqt=#letters-seqt+1 end if exremember and i<#sel then tab=savetab[rvrs] ex1=tab.x1 ex2=tab.x2 else if xprop then xhdist=(ex1b-ex1a)/#letters ex1=round(ex1a+xhdist*xind) else ex1=math.ceil(math.random(ex1a,ex1b)) end if yprop then xvdist=(ex2b-ex2a)/#letters ex2=round(ex2a+xvdist*yind) else ex2=math.ceil(math.random(ex2a,ex2b)) end end if exremember and i==#sel then table.insert(savetab,{x1=ex1,x2=ex2}) end

-- move sequence if exseq then tfrag=round(dur/#letters/(100/seqpc)) xt1=tfrag*seqt-tfrag else xt1=0 end xt2=dur if implode and exseq then xt2=dur-xt1 xt1=0 end txt2=txt2:gsub("\\move%(([%d%.%-]+),([%d%.%-]+).-%)","\\pos(%1,%2)") txt2=txt2:gsub("\\fad%(.-%)","") if implode then txt2=txt2:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)", function(a,b) return EFO.."\\move("..a+ex1..","..b+ex2..","..a..","..b..","..xt1..","..xt2..")" end) else txt2=txt2:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)", function(a,b) return EFO.."\\move("..a..","..b..","..a+ex1..","..b+ex2..","..xt1..","..xt2..")" end) end txt2=txt2:gsub("{\\[^}]-}$","") end l2.text=txt2 if letters[l]~=" " then subs.insert(sel[i]+1,l2) table.insert(sel,sel[#sel]+i) end end line.comment=true end

-- Clone Clip if res.stuff=="clone clip" and text:match("\\clip%((.-)%)") then text=text:gsub("\\clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) a=math.floor(a) b=math.floor(b) c=math.ceil(c) d=math.ceil(d) return string.format("\\clip(m %d %d l %d %d %d %d %d %d)",a,b,c,b,c,d,a,d) end) clip=text:match("\\clip%((.-)%)") clip=clip.." " h_clip=clip for h=1,clone_h-1 do hc=clip:gsub("([%d%-]+) ([%d%-]+)",function(a,b) return a+dist_h*h.." "..b end) h_clip=h_clip..hc end fullclip=h_clip for v=1,clone_v-1 do if v%2==1 then offset=ccshift else offset=0 end vc=h_clip:gsub("([%d%-]+) ([%d%-]+)",function(a,b) return a+offset.." "..b+dist_v*v end) fullclip=fullclip..vc end fullclip=fullclip:gsub(" $","") text=text:gsub("\\clip%((.-)%)","\\clip("..fullclip..")") end

-- DISSOLVE Individual Lines ------if res.stuff=="dissolve text" then

fullklip="" -- radius of clips based on # of sel. lines and shapes r=math.ceil(i*linez/#sel-1) if shape=="diamond" and not alternate then r=math.floor(r*1.5) end if shape:match("triangle") and alternate then r=math.floor(r*1.4) end if shape:match("triangle") and not alternate then r=math.floor(r*1.5) end if shape=="wave/hexagram" then r=math.floor(r*1.55) end xpt=0 sw=0 osq=0

if not dis2 and not shape:match("lines") and r>0 then for w=1,#allpoints do pt=allpoints[w]

if shape=="square" or shape=="square 2" then krip="m "..pt[1]-r.." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]-r.." "..pt[1]+r.." "..pt[2]+r.." "..pt[1]-r.." "..pt[2]+r.." " end

if shape=="diamond" then krip="m "..pt[1].." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2].." "..pt[1].." "..pt[2]+r.." "..pt[1]-r.." "..pt[2].." " end

if shape=="triangle 1" then krip="m "..pt[1].." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]+r.." "..pt[1]-r.." "..pt[2]+r.." " end

if shape=="triangle 2" then krip="m "..pt[1]-r.." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]-r.." "..pt[1].." "..pt[2]+r.." " end

if shape=="hexagon" then krip="m "..pt[1].." "..pt[2]-2*r.." l "..pt[1]+2*r.." "..pt[2]-r.." "..pt[1]+2*r.." "..pt[2]+r.." "..pt[1].." "..pt[2]+2*r.." "..pt[1]-2*r.." "..pt[2]+r.." "..pt[1]-2*r.." "..pt[2]-r.." " end

if shape=="wave/hexagram" then if sw==0 then krip="m "..pt[1].." "..pt[2]-r+1 .." l "..pt[1]+r.." "..pt[2]+r+1 .." "..pt[1]-r.." "..pt[2]+r+1 .." " else krip="m "..pt[1]-r.." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]-r.." "..pt[1].." "..pt[2]+r.." " end xpt=xpt+1 if xpt==#xpoints then xpt=0 sw=1-sw end end

fullklip=fullklip..krip end end

if not dis2 and shape=="vertical lines" and r>0 then for w=1,#xpoints do pt=xpoints[w] krip="m "..pt-r.." "..y1.." l "..pt+r.." "..y1.." "..pt+r.." "..y2.." "..pt-r.." "..y2.." " fullklip=fullklip..krip end end

if not dis2 and shape=="horizontal lines" and r>0 then for w=1,#ypoints do pt=ypoints[w] krip="m "..x1-vert.." "..pt-r.." l "..x2+vert.." "..pt-r.." "..x2+vert.." "..pt+r.." "..x1-vert.." "..pt+r.." " fullklip=fullklip..krip end end

if dis2 and r>0 then fullklip=dis2tab[r] end

fullklip=fullklip:gsub(" $","")

text=text:gsub("\\clip%(.-%)","") if r>0 then text=addtag("\\iclip("..fullklip..")",text) end end -- DISSOLVE END 2 ------

-- RANDOMIZED TRANSFORMS ------if res.stuff=="randomized transforms" then dur=line.end_time-line.start_time if RTMax then MxF=dur end

-- Fade/Duration if RTM=="FD" then FD=math.random(MnF,MxF) if RTD and not RTin then line.end_time=line.start_time+FD end if RTD and RTin then line.start_time=line.end_time-FD end if RTF and not RTin then text="{\\fad(0,"..FD..")}"..text text=text:gsub(FD.."%)}{",FD..")") end if RTF and RTin then text="{\\fad("..FD..",0)}"..text text=text:gsub(",0%)}{",",0)") end end

-- Number Transform if RTM=="NT" then NT=math.random(MnT*10,MxT*10)/10 if RTA then NTA=math.random(MnA*10,MxA*10)/10 axel=NTA.."," else axel="" end text=addtag("\\t("..axel.."\\"..RTT..NT..")",text) end

-- Colour Transform if RTM=="CT" then CTfull="" for c=1,#rtcol do ctype="\\"..rtcol[c].."c" ctype=ctype:gsub("\\1c","\\c") Bluu,Grin,Rett=text:match("^{[^}]-"..ctype.."&H(%x%x)(%x%x)(%x%x)&") if Bluu~=nil then R=tonumber(Rett,16) G=tonumber(Grin,16) B=tonumber(Bluu,16) Red=math.random(R-MxC,R+MxC) Green=math.random(G-MxC,G+MxC) Blue=math.random(B-MxC,B+MxC) else Red=math.random(0,255) Green=math.random(0,255) Blue=math.random(0,255) end CT=ctype.."&H"..tohex(Blue)..tohex(Green)..tohex(Red).."&" CTfull=CTfull..CT end if RTA then NTA=math.random(MnA*10,MxA*10)/10 axel=NTA.."," else axel="" end if CTfull~="" then text=addtag("\\t("..axel..CTfull..")",text) end end

-- Move X if rez.rtmx then MMX=math.random(MnX,MxX) text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) if RTin then a=a+MMX else c=c+MMX end return "\\move("..a..","..b..","..c..","..d end) text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)", function(a,b) a2=a if RTin then a=a+MMX else a2=a2+MMX end return "\\move("..a..","..b..","..a2..","..b end) end

-- Move Y if rez.rtmy then MMY=math.random(MnY,MxY) text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) if RTin then b=b+MMY else d=d+MMY end return "\\move("..a..","..b..","..c..","..d end) text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)", function(a,b) b2=b if RTin then b=b+MMY else b2=b2+MMY end return "\\move("..a..","..b..","..a..","..b2 end) end end if res.stuff=="time by frames" and i>1 then line.start_time=fr2ms(fstart+(i-1)*frs) line.end_time=fr2ms(fendt+(i-1)*fre) end

-- DUPLICATE AND SHIFT LINES if res.stuff=="duplicate and shift lines" then SF=ms2fr(line.start_time) EF=ms2fr(line.end_time) l2=line effect=line.effect for x=FALL,1,-1 do F=x-FB1 -- Main line if F==0 then l2.start_time=fr2ms(SF) l2.end_time=fr2ms(EF) l2.effect=effect.."[0]" l2.text=text end -- Before if F<0 then l2.start_time=fr2ms(SF+F) l2.end_time=fr2ms(SF+F+1) l2.effect=effect.."["..F.."]" l2.text=text :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+).-%)","\\pos(%1,%2)") :gsub("\\t%b()","") :gsub("\\fad%b()","") end -- After if F>0 then l2.start_time=fr2ms(EF+F-1) l2.end_time=fr2ms(EF+F) l2.effect=effect.."["..F.."]" l2.text=text :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+).-%)","\\pos(%3,%4)") :gsub("\\t%b()",function(t) return t:gsub("\\t%([^\\]*",""):gsub("%)$","") end) :gsub("\\fad%b()","") l2.text=l2.text:gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) end if res.rep3=="0" then l2.effect=effect end subs.insert(sel[#sel]+1,l2) end end

if res.stuff=="fix kara tags for fbf lines" then KOD=line.start_time-KO1 if KOD>0 then LINE={} for k in text:gmatch("{[^}]-}[^{]*") do table.insert(LINE,k) end for K=1,#LINE do seg=LINE[K] seg=seg:gsub("^(.-)(\\k[of]?)([%d%.]+)(.-)$",function(s,t,k,e) k=k*10 KOD=KOD-k if KOD>=0 then return s..e end if KOD<0 and k+KOD>0 then k=(k+KOD)/10 return s..t..k..e end if KOD<0 and k+KOD<0 then k=k/10 return s..t..k..e end end) LINE[K]=seg:gsub("{}","") end nt="" for K=1,#LINE do nt=nt..LINE[K] end text=nt end end

line.text=text subs[sel[i]]=line if res.stuff=="split into letters" or res.stuff=="explode" and not rez.excom then subs.delete(sel[i]) table.remove(sel,#sel) end if res.stuff=="what is the Matrix?" then subs.delete(sel[i]) end end if res.stuff:match"replacer" then aegisub.progress.task("All stuff has been finished.") if repl==1 then rp=" modified line" else rp=" modified lines" end press,reslt=ADD({},{repl..rp},{cancel=repl..rp}) end if res.stuff=="duplicate and shift lines" then SEL=#sel for i=#sel,1,-1 do subs.delete(sel[i]) end for x=1,(FA+FB)*SEL do table.insert(sel,sel[SEL]+x) end end if res.stuff=="format dates" and rez.log then aegisub.log(datelog) end if noclip then t_error("Some lines weren't processed - missing clip.") noclip=nil end savetab=nil return sel end function fill_in(tags,tag) if tag=="\\bord" then tags=tags:gsub("^{","{"..tag..styleref.outline) elseif tag=="\\shad" then tags=tags:gsub("^{","{"..tag..styleref.shadow) elseif tag=="\\fscx" then tags=tags:gsub("^{","{"..tag..styleref.scale_x) elseif tag=="\\fscy" then tags=tags:gsub("^{","{"..tag..styleref.scale_y) elseif tag=="\\fsize" then tags=tags:gsub("^{","{"..tag..styleref.fontsize) elseif tag=="\\fsp" then tags=tags:gsub("^{","{"..tag..styleref.spacing) elseif tag=="\\alpha" then tags=tags:gsub("^{","{"..tag.."&H00&") elseif tag=="\\1a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color1:match("H%x%x").."&") elseif tag=="\\2a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color2:match("H%x%x").."&") elseif tag=="\\3a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color3:match("H%x%x").."&") elseif tag=="\\4a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color4:match("H%x%x").."&") elseif tag=="\\c" then tags=tags:gsub("^{","{"..tag..styleref.color1:gsub("H%x%x","H")) elseif tag=="\\2c" then tags=tags:gsub("^{","{"..tag..styleref.color2:gsub("H%x%x","H")) elseif tag=="\\3c" then tags=tags:gsub("^{","{"..tag..styleref.color3:gsub("H%x%x","H")) elseif tag=="\\4c" then tags=tags:gsub("^{","{"..tag..styleref.color4:gsub("H%x%x","H")) else tags=tags:gsub("^{","{"..tag.."0") end return tags end

-- Jump to Next -- function nextsel(subs,sel) lm=nil i=sel[1] marks={} for x,i in ipairs(sel) do rine=subs[i] txt=rine.text:gsub("{[^}]-}","") if res.field=="text" then mark=txt end if res.field=="style" then mark=rine.style end if res.field=="actor" then mark=rine.actor end if res.field=="effect" then mark=rine.effect end if res.field=="layer" then mark=rine.layer end if mark=="" then mark="_empty_" end if mark~=lm then table.insert(marks,mark) end lm=mark end count=1 repeat line=subs[i+count] txt2=line.text:gsub("{[^}]-}","")

if res.field=="text" then hit=txt2 end if res.field=="style" then hit=line.style end if res.field=="actor" then hit=line.actor end if res.field=="effect" then hit=line.effect end if res.field=="layer" then hit=line.layer end if hit=="" then hit="_empty_" end ch=0 for m=1,#marks do if marks[m]==hit then ch=1 end end if ch==0 or i+count==#subs then sel={i+count} end count=count+1 until ch==0 or hit==nil or i+count>#subs return sel end -- Alpha Shift -- function alfashift(subs,sel) count=1 for x, i in ipairs(sel) do line=subs[i] text=line.text if not text:match("{\\alpha&HFF&}[%w%p]") then t_error("Line "..x.." does not \nappear to have \n\\alpha&&HFF&&",true) end if count>1 then switch=1 repeat text=text :gsub("({\\alpha&HFF&})([%w%p])","%2%1") :gsub("({\\alpha&HFF&})(%s)","%2%1") :gsub("({\\alpha&HFF&})(\\N)","%2%1") :gsub("({\\alpha&HFF&})$","") switch=switch+1 until switch>=count end count=count+1 line.text=text subs[i]=line end end

-- Merge tags -- function merge(subs,sel) tk={} tg={} stg="" for x, i in ipairs(sel) do line=subs[i] text=line.text text=text:gsub("{\\\\k0}","") repeat text,c=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until c==0 vis=text:gsub("{[^}]-}","") if x==1 then rt=vis ltrmatches=re.find(rt,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end end if vis~=rt then t_error("Error. Inconsistent text.",true) end stags=text:match("^{(\\[^}]-)}") or "" stg=stg..stags stg=duplikill(stg) text=text:gsub("^{\\[^}]-}","") :gsub("{[^\\}]-}","") count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t newt=duplikill(newt) newt=newt:gsub("%*$","") end end if newt~="" then newline=newline.."{"..newt.."}" end end newtext="{"..stg.."}"..newline newtext=extrakill(newtext,2) line=subs[sel[1]] line.text=newtext subs[sel[1]]=line for i=#sel,2,-1 do subs.delete(sel[i]) end sel={sel[1]} return sel end function textmod(orig,text) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until not text:match("{(\\[^}]-)}{(\\[^}]-)}") vis=text:gsub("{[^}]-}","") ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end stags=text:match("^{(\\[^}]-)}") if stags==nil then stags="" end text=text:gsub("^{\\[^}]-}","") :gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext="{"..stags.."}"..newline text=newtext return text end

-- Honorificslaughterhouse -- function honorifix(subs,sel) for i=#subs,1,-1 do if subs[i].class=="dialogue" then line=subs[i] text=line.text text=text :gsub("%-san","{-san}") :gsub("%-chan","{-chan}") :gsub("%-kun","{-kun}") :gsub("%-sama","{-sama}") :gsub("%-niisan","{-niisan}") :gsub("%-oniisan","{-oniisan}") :gsub("%-oniichan","{-oniichan}") :gsub("%-oneesan","{-oneesan}") :gsub("%-oneechan","{-oneechan}") :gsub("%-neesama","{-neesama}") :gsub("%-sensei","{-sensei}") :gsub("%-se[mn]pai","{-senpai}") :gsub("%-dono","{-dono}") :gsub("Onii{%-chan}","Brother{Onii-chan}") :gsub("Onii{%-san}","Brother{Onii-san}") :gsub("Onee{%-chan}","Sister{Onee-chan}") :gsub("Onee{%-san}","Sister{Onee-san}") :gsub("Onee{%-sama}","Sister{Onee-sama}") :gsub("onii{%-chan}","brother{onii-chan}") :gsub("onii{%-san}","brother{onii-san}") :gsub("onee{%-chan}","sister{onee-chan}") :gsub("onee{%-san}","sister{onee-san}") :gsub("onee{%-sama}","sister{onee-sama}") :gsub("{{","{") :gsub("}}","}") :gsub("({[^{}]-){(%-%a-)}([^{}]-})","%1%2%3") line.text=text subs[i]=line end end end

-- framerate -- function framerate(subs) f1=res.fps1 f2=res.fps2 for i=1, #subs do if subs[i].class=="dialogue" then local line=subs[i] line.start_time=line.start_time/f2*f1 line.end_time=line.end_time/f2*f1 subs[i]=line end end end

-- reanimatools -- function string2line(str) local ltype,layer,s_time,e_time,style,actor,margl,margr,margv,eff,txt=str:match("(%a+): (%d+),([^,]-),([^,]-),([^,]- ),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),(.*)") l2={} l2.class="dialogue" if ltype=="Comment" then l2.comment=true else l2.comment=false end l2.layer=layer l2.start_time=string2time(s_time) l2.end_time=string2time(e_time) l2.style=style l2.actor=actor l2.margin_l=margl l2.margin_r=margr l2.margin_t=margv l2.effect=eff l2.text=txt return l2 end function string2time(timecode) timecode=timecode:gsub("(%d):(%d%d):(%d%d)%.(%d%d)",function(a,b,c,d) return d*10+c*1000+b*60000+a*3600000 end) return timecode end function esc(str) str=str :gsub("%%","%%%%") :gsub("%(","%%%(") :gsub("%)","%%%)") :gsub("%[","%%%[") :gsub("%]","%%%]") :gsub("%.","%%%.") :gsub("%*","%%%*") :gsub("%-","%%%-") :gsub("%+","%%%+") :gsub("%?","%%%?") return str end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("(\\i?clip%b())(.-)(\\i?clip%b())", function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) until c==0 tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function cleantr(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..trnsfrm.."}") return tags end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) if num<1 then num="0" elseif num>14 then num="F" elseif num==10 then num="A" elseif num==11 then num="B" elseif num==12 then num="C" elseif num==13 then num="D" elseif num==14 then num="E" end return num end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function stylechk(subs,stylename) for i=1, #subs do if subs[i].class=="style" then local st=subs[i] if stylename==st.name then styleref=st break end end end return styleref end function getpos(subs,text) for i=1,#subs do if subs[i].class=="info" then local k=subs[i].key local v=subs[i].value if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if resx==nil then resx=0 end if resy==nil then resy=0 end if subs[i].class=="style" then local st=subs[i] if st.name==line.style then acleft=st.margin_l if line.margin_l>0 then acleft=line.margin_l end acright=st.margin_r if line.margin_r>0 then acright=line.margin_r end acvert=st.margin_t if line.margin_t>0 then acvert=line.margin_t end acalign=st.align if text:match("\\an%d") then acalign=text:match("\\an(%d)") end aligntop="789" alignbot="123" aligncent="456" alignleft="147" alignright="369" alignmid="258" if alignleft:match(acalign) then horz=acleft h_al="left" elseif alignright:match(acalign) then horz=resx-acright h_al="right" elseif alignmid:match(acalign) then horz=resx/2 h_al="mid" end if aligntop:match(acalign) then vert=acvert v_al="top" elseif alignbot:match(acalign) then vert=resy-acvert v_al="bottom" elseif aligncent:match(acalign) then vert=resy/2 v_al="mid" end break end end end if horz>0 and vert>0 then if not text:match("^{\\") then text="{\\rel}"..text end text=text:gsub("^({\\[^}]-)}","%1\\pos("..horz..","..vert..")}") :gsub("\\rel","") end return text end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end function round(num) num=math.floor(num+0.5) return num end function logg(m) return aegisub.log("\n "..m) end

-- Config Stuff -- function saveconfig() unconf="Unimportant Configuration\n\n" for key,val in ipairs(unconfig) do if val.class=="floatedit" or val.class=="dropdown" then unconf=unconf..val.name..":"..res[val.name].."\n" end if val.class=="checkbox" and val.name~="save" then unconf=unconf..val.name..":"..tf(res[val.name]).."\n" end end unimpkonfig=ADP("?user").."\\unimportant.conf" file=io.open(unimpkonfig) if file~=nil then konf=file:read("*all") io.close(file) imp1=konf:match("imp1:(.-)\n") imp2=konf:match("imp2:(.-)\n") imp3=konf:match("imp3:(.-)\n") chap1=konf:match("chap1:(.-)\n") chap2=konf:match("chap2:(.-)\n") chap3=konf:match("chap3:(.-)\n") end if imp1==nil then imp1="relative" end if imp2==nil then imp2="" end if imp3==nil then imp3="D:\\typesetting\\" end if chap1==nil then chap1="relative" end if chap2==nil then chap2="" end if chap3==nil then chap3="D:\\typesetting\\" end savestuff={ {x=0,y=0,class="label",label="Import script path:"}, {x=0,y=1,class="label",label="Import relative path:"}, {x=0,y=2,class="label",label="Import absolute path:"}, {x=0,y=3,class="label",label="Chapters save path:"}, {x=0,y=4,class="label",label="Chapters relative path:"}, {x=0,y=5,class="label",label="Chapters absolute path:"}, {x=1,y=0,class="dropdown",name="imp1",items={"relative","absolute"},value=imp1}, {x=1,y=1,class="edit",width=16,name="imp2",value=imp2}, {x=1,y=2,class="edit",width=16,name="imp3",value=imp3}, {x=1,y=3,class="dropdown",name="chap1",items={"relative","absolute"},value=chap1}, {x=1,y=4,class="edit",width=16,name="chap2",value=chap2}, {x=1,y=5,class="edit",width=16,name="chap3",value=chap3}, }

click,rez=ADD(savestuff,{"Save","Cancel"},{ok='Save',close='Cancel'}) if click=="Cancel" then ak() end rez.imp3=rez.imp3:gsub("[^\\]$","%1\\") rez.chap3=rez.chap3:gsub("[^\\]$","%1\\")

for key,val in ipairs(savestuff) do if val.x==1 then unconf=unconf..val.name..":"..rez[val.name].."\n" end end file=io.open(unimpkonfig,"w") file:write(unconf) file:close() ADD({{class="label",label="Config saved to:\n"..unimpkonfig}},{"OK"},{close='OK'}) end function loadconfig() unimpkonfig=ADP("?user").."\\unimportant.conf" file=io.open(unimpkonfig) if file~=nil then konf=file:read("*all") io.close(file) if konf:match("^%-%-") then konf="" else for key,val in ipairs(unconfig) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="dropdown" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end if lastimp and val.name=="stuff" then val.value=lastuff end if lastimp and val.name=="log" then val.value=lastlog end if lastimp and val.name=="zeros" then val.value=lastzeros end if lastimp and val.name=="field" then val.value=lastfield end end end end script_path=konf:match("imp1:(.-)\n") if script_path==nil then script_path="relative" end relative_path=konf:match("imp2:(.-)\n") if relative_path==nil then relative_path="" end absolute_path=konf:match("imp3:(.-)\n") if absolute_path==nil then absolute_path="D:\\typesetting\\" end ch_script_path=konf:match("chap1:(.-)\n") if ch_script_path==nil then ch_script_path="relative" end ch_relative_path=konf:match("chap2:(.-)\n") if ch_relative_path==nil then ch_relative_path="" end ch_absolute_path=konf:match("chap3:(.-)\n") if ch_absolute_path==nil then ch_absolute_path="D:\\typesetting\\" end end end function tf(val) if val==true then ret="true" else ret="false" end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end

function analyze(l) text=l.text dur=l.end_time-l.start_time dura=dur/1000 txt=text:gsub("{[^}]-}","") :gsub("\\N","") visible=text:gsub("{\\alpha&HFF&}[^{}]-{[^{}]-}","") :gsub("{\\alpha&HFF&}[^{}]*$","") :gsub("{[^{}]- }","") :gsub("\\[Nn]","*") :gsub("%s?%*+%s?"," ") :gsub("^%s+","") :gsub("%s+$","") wrd=0 for word in txt:gmatch("([%a\']+)") do wrd=wrd+1 end chars=visible:gsub(" ","") :gsub("[%.,\"]","") char=chars:len() cps=math.ceil(char/dura) if dur==0 then cps=0 end end function info(subs,sel,act) styletab={} dc=0 sdur=0 S=subs[sel[1]].start_time E=subs[sel[#sel]].end_time video=nil stitle=nil colorspace=nil resx=nil resy=nil prop=aegisub.project_properties() for x,i in ipairs(sel) do line=subs[i] dur=line.end_time-line.start_time if line.start_timeE then E=line.end_time end sdur=sdur+dur end seldur=sdur/1000 for i=1, #subs do if subs[i].class=="info" then local k=subs[i].key local v=subs[i].value if k=="Title" then stitle=v end if k=="Video File" then video=v end if k=="YCbCr Matrix" then colorspace=v end if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if video==nil then video=prop.video_file:gsub("^.*\\","") end if stitle==nil then sct="" else sct="Script title: "..stitle.."\n" end if video==nil then vf="" else vf="Video file: "..video.."\n" end if resy==nil then reso="" else reso="Script resolution: "..resx.."x"..resy.."\n" end if colorspace==nil then cols="" else cols="Colorspace: "..colorspace.."\n" end nfo=sct..vf..reso..cols if subs[i].class=="style" then local s=subs[i] table.insert(styletab,s) end if subs[i].class=="dialogue" then dc=dc+1 local l=subs[i] if i==act then ano=dc analyze(l) for s=1,#styletab do st=styletab[s] if st.name==l.style then acfont=st.fontname acsize=st.fontsize acalign=st.align acleft=st.margin_l acright=st.margin_r acvert=st.margin_t acbord=st.outline acshad=st.shadow if st.bold then actbold="Bold" else actbold="Regular" end end end aligntop="789" alignbot="123" aligncent="456" alignleft="147" alignright="369" alignmid="258" if aligntop:match(acalign) then vert=acvert elseif alignbot:match(acalign) then vert=resy-acvert elseif aligncent:match(acalign) then vert=resy/2 end if alignleft:match(acalign) then horz=acleft elseif alignright:match(acalign) then horz=resx-acright elseif alignmid:match(acalign) then horz=resx/2 end

aktif="Active line: "..ano.."\nStyle used: "..l.style.."\nFont used: "..acfont.."\nWeight: "..actbold.."\nFont size: "..acsize.."\nBorder: "..acbord.."\nShadow: "..acshad.."\nDuration: "..dura.."s\nCharacters: "..char.."\nCharacters per second: "..cps.."\nDefault position: "..horz..","..vert.."\n\nVisible text:\n"..visible end end

end infodump=nfo.."Styles used: "..#styletab.."\nDialogue lines: "..dc..", Selected: "..#sel.."\nCombined length of selected lines: "..seldur.."s\nSelection duration: "..(E-S)/1000 .."s\n\n"..aktif end help_i=[[ - IMPORT/EXPORT -

This allows you to import OP/ED or signs (or whatever) from an external .ass file. OP/ED must be saved as OP.ass and ED.ass; a sign can have any name. The .ass file may contain headers, or it can be just the dialogue lines. The imported stuff will be shifted to your currently selected line (or the first one in your selection). The first line of the saved file works as a reference point, so use a "First frame of OP" line etc. (You can save your OP/ED shifted to 0 or you can just leave it as is; the times will be recalculated to start at the current line.) "keep line" will keep your current line and comment it. Otherwise the line gets deleted.

IMPORT SIGN / IMPORT SIGNS - works like OP/ED, but you have to input the sign's name. The difference between the two is: SIGN - each sign must be saved in its own .ass file. In the GUI, input the sign's/file's name, for example "eptitle"[.ass]. SIGNS - all signs must be saved in signs.ass. They are distinguished by what's in the "effect" field - that's the sign's name. For SIGN, make something like eptitle.ass, eyecatch.ass; for SIGNS, put "eptitle" or "eyecatch" in the effect field, and put all the signs in signs.ass. (You can have blank lines between signs for clarity. The script can deal with those.) The GUI will then show you a list of signs that it gets from the effect fields. I recommend using SIGNS, as it's imo more efficient (but SIGN was written first and I didn't nuke it).

Options: With nothing checked, stuff is shifted to the first frame of your active line (like OP/ED). (SIGN) File name: "custom" will use what you type below. The other ones are presets. "keep current line's times" - all imported lines will have the start/end time of your active line "keep current line's text" - all imported lines will have their text (not tags) replaced with your active line's text - If you want to replace only some lines and keep others, like masks, put 'x' in actor field of the mask. "combine tags (current overrides)" - tags from current + imported line get combined (current overrides imported) "combine tags (imported overrides)" - same as above, but imported overrides current - Both of these will also be ignored for imported lines that have "x" in actor field. "don't shift times" - times of imported lines will be kept as they were saved "delete original line" - this overrides the "keep line" option in the main menu. (I thought it would be convenient to have it here.)

EXPORT SIGN - Saves the selected sign(s) either to 'signs.ass' or to a new file. Effect field must contain the signs' names.

You can use relative or absolute paths. (Check the settings below.) Default is the script's folder. If you want the default to be one folder up, use "..\". You can use an absolute path, have one huge signs.ass there, and have all the signs marked "show_name-sign_name" in the effect field.

IMPORT CHPTRS - Imports chapters from xml files - creates lines with "chptr" in actor and {ch. name} as text]] help_u=[[ UPDATE LYRICS

This is probably the most complicated part, but if your songs have some massive styling with layers + tracking, this will make updating lyrics, which would otherwise be a pain in the ass, really easy. The only styling that will prevent this from working is inline tags - gradient by character etc.

The prerequisite here is that your OP/ED MUST have NUMBERED lines! (See NUMBERS section - might be good to read that first.) The numbers must correspond to the verses, not to lines in the script. If line 1 of the SONG is mocha-tracked over 200 frames, all of those frames must be numbered 01. It is thus most convenient to number the lines before you start styling, when it's still simple.

How this works: Paste your updated lyrics into the large, top-left area of the GUI. Use the Left and Right fields to set the markers to detect the right lines. Without markers it will just look for numbers. If your OP lines are numbered with "OP01eng", you must set "OP" under Left and "eng" under Right. For now, everything is case-sensitive (I might change that later if it gets really annoying and pointless). You must also correctly set the actor/effect choice in the bottom-right part of the GUI. If you pasted lyrics, selected "update lyrics", and set markers and actor/effect. Then hit Import, and lyrics will be updated.

How it works - example: The lyrics you pasted in the data box get their lines assigned with numbers from 1 to whatever. Let's say your markers are "OP01eng" and you're using the effect field. The script looks for lines with that pattern in the effect field. When it finds one, it reads the number (for example "01" from "OP01eng") and replaces the line's text (skipping tags) with line 1 from the pasted lyrics. For every line marked "OP##eng" it replaces the current lyrics with line ## from your pasted updated lyrics.

To make sure this doesn't fuck up tremendously, it shows you a log with all replacements at the end.

That's pretty much all you really need to know for updating lyrics, but there are a few more things.

If the script doesn't find any lines that match the markers, it gives you a message like this: "The effect field of selected lines doesn't match given pattern..." This means the lines either don't exist in your selection, or you probably forgot to set the markers.

"style restriction" is an extra option that lets you limit the replacing to lines whose style contains given pattern. Let's give some examples: You check the restriction and type "OP" in the field below. You can now select the whole script instead of selecting only the OP lines, and only lines with "OP" in style will be updated. You may have the ED numbered the same way, but the "OP" restriction will ignore it. This can be also useful if you have lines numbered just 01, 02 etc., and you have english and romaji, all mixed together. If your styles are OP-jap and OP-eng, you can type "jap" in the restriction field if you're updating romaji to make sure the script doesn't update the english lines as well (replacing them with romaji). It is, however, recommended to just use different markers, like j01 / e01.]] help_c=[[ - CHAPTERS -

This will generate chapters from the .ass file

MARKER: For a line to be used for chapters, it has to be marked with "chapter"/"chptr"/"chap" in actor/effect field (depending on settings) or the same 3 options as a separate comment, ie. {chapter} etc. CHAPTER NAME: What will be used as chapter name. It's either the content of the effect field, or the line's FIRST comment. If the comment is {OP first frame} or {ED start}, the script will remove " first frame" or " start", so you can keep those.

If you use default settings, just put "chapter" in actor field and make comments like {OP} or {Part A}.

Subchapters: You can make subchapters like this {Part A::Scene 5}. This will be a subchapter of "Part A" called "Scene 5".

If you want a different LANGUAGE than 'eng', set it in the textbox below "chapter mark"

CHAPTER MARK: Sets the selected chapter for selected line(s). Uses marker and name. (Doesn't create xml.) If you want a custom chapter name, type it in the textbox below this. mp4-compatible chapters: switches to this format: CHAPTER01=00:00:00.033 CHAPTER01NAME=Intro]] help_n=[[ - NUMBERS -

This is a tool to number lines and add various markers to actor/effect fields. The dropdown with "01" lets you choose how many leading zeros you want. The Left and Right fields will add stuff to the numbers. If Left is "x" and Right is "yz", the first marker will be "x01yz". What makes this function much more versatile is the "Mod" field. If you put in one number, then that's the number from which the numbering will start, so "5" -> 5, 6, 7, etc. You can, however, use a comma or slash to modify the numbering some more. "8,3" or "8/3" will start numbering from 8, repeating each number 3 times, so 8, 8, 8, 9, 9, 9, 10, 10, 10, etc. This allows you to easily number lines that are typeset in layers etc. Additionally, you can set a limit in [], for example 1/3[2], which will start from 1, use each number 3 times, and only go up to 2 and then start again, so: 1 1 1 2 2 2 1 1 1 2 2 2 2/3[4] would give you 2 2 2 3 3 3 4 4 4 2 2 2 3 3 3 4 4 4 ...

"add to marker" uses the Left and Right fields to add stuff to the current content of actor/effect/text. If you number lines for the OP, you can set "OP-" in Left and "-eng" in Right to get "OP-01-eng". (Mod does nothing when adding markers.)]] help_d=[[ - DO STUFF -

- Save/Load - You can use this to save for example bits of text you need to paste frequently (like a multi-clipboard). Paste text in the data area to save it. If the data area is empty, the function will load your saved texts.

- Lua Replacer - Use "Left" and "Right" for a lua regexp replace function.

- Perl Replacer - Use "Left" and "Right" for a perl regexp replace function.

- Lua Calc - Use "Left" and "Right" with lua regexp to perform calculations on captured numbers. Captures will be named a, b, c... up to p (16 captures max). Functions are +, -, *, /, and round(a), which rounds the number captured in a. > Example: (%d)(%d)(%d) -> a+1b*2c-3 This will match 3-digit patterns, add 1 to first digit, multiply the second by 2, and subtract 3 from the 3rd. If you want to leave one of the captures as is, use .. to separate it from other letters: a+1b..c-3 > Example: pos%(([%d%.]+),([%d%.]+) -> pos(a+50,b-100 This will shift position right by 50 and up by 100.

- Jump to Next - This is meant to get you to the "next sign" in the subtitle grid. When mocha-tracking 1000+ lines, it can be a pain in the ass to find where one sign ends and another begins. Select lines that belong to the current "sign", ie. different layers/masks/texts. The script will search for the first line in the grid that doesn't match any of the selected ones, based on the "Marker".

- Alpha Shift - Shifts {\alpha&HFF&} by one letter for each line. Text thus appears letter by letter. It's an alternative to the script that spawns \ko, but this works with shadow too. Duplicate a line with {\alpha&HFF&} however many times you need and run the script on the whole selection.

- Motion Blur - Creates motion blur by duplicating the line and using some alpha. By default you keep the existing blur for each line, but you can set a value to override all lines. 'Distance' is the distance between the \pos coordinates of the resulting 2 lines. If you use 3 lines, the 3rd one will be in the original position, i.e. in the middle. The direction is determined from the first 2 points of a vectorial clip (like with clip2frz/clip2fax).

- Merge Tags - Select lines with the same text but different tags, and they will be merged into one line with tags from all of them. For example: {\bord2}AB{\shad3}C A{\fs55}BC -> {\bord2}A{\fs55}B{\shad3}C If 2 lines have the same tag in the same place, the value of the later line overrides the earlier one.

- Add Comment - Text that you type here in this box will be added as a {comment} at the end of selected lines.

- Make Comments Visible - Nukes { } from comments, thus making them part of the text visible on screen.

- Switch Commented/Visible - Comments out what's visible and makes visible what's commented. Allows switching between two texts.

- Reverse Text - Reverses text (character by character). Nukes comments and inline tags.

- Reverse Words - Reverses text (word by word). Nukes comments and inline tags.

- Reverse Transforms - \blur1\t(\blur3) becomes \blur3\t(\blur1). Only for initial tags. Only one transform for each tag. - Fake Capitals - Creates fake capitals by increasing font size for first letters. With all caps, for first letters of words. With mixed text, for uppercase letters. Set the \fs for the capitals in the Left field. Looks like this: {\fs60}F{\fs}AKE {\fs60}C{\fs}APITALS

- Format Dates - Formats dates to one of 4 options. Has its own GUI. Only converts from the other 3 options in the GUI.

- Split into Letters - Makes a line for each letter, making the other letters invisible with alpha. This lets you do things with each letter separately.

- Explode - This splits the line into letters and makes each of them move in a different direction and fade out.

- Dissolve Text - Various modes of dissolving text. Has its own Help.

- Duplicate and Shift Lines - Duplicates selected lines as many times you want before and/or after the current line. Use Left/Right fields to set how many frames should be duplicated before/after the line. \move and \t --> lines before get \pos with start coordinates and state before transforms; lines after get end coordinates and state after transforms. Lines are automatically numbered in Effect field. You can disable that by typing 0 in Mod field.

- Randomized Transforms - Various modes of randomly transforming text. Has its own Help.

- Fix Kara Tags for fbf Lines - If you need to split a line with kara tags, this adjusts the tags so that the text appears continuously as it should. Selection must include the first line for reference. Applies to all karaoke tags indiscriminately - \k, \kf, \ko.

- Clone Clip - Clones/replicates a clip you draw. Set how many rows/columns and distances between them, and you can make large patterns.

- Time by Frames - Left = frames to shift start time by, each line (2 = each new line starts 2 frames later than previous) Right = frames to shift end time by, each line (4 = each new line ends 4 frames later than previous)

- Honorificslaughterhouse - Comments out honorifics.

- Convert Framerate - Converts framerate from a to b where a is the input from "Left" and b is input from "Right". ]]

-- Unimportant GUI ------function unimportant(subs,sel,act) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel aegisub.progress.title("Loading Unimportant Stuff") aegisub.progress.task("This should take less than a second, so you won't really read this.") if datata==nil then data="" else data=datata end if sub1==nil then sub1="" end if sub2==nil then sub2="" end if sub3==nil then sub3=1 end msg={"If it breaks, it's your fault.","This should be doing something...","Breaking your computer. Please wait.","Unspecified operations in progress.","This may or may not work.","Trying to avoid bugs...","Zero one one zero one zero...","10110101001101101010110101101100001","I'm surprised anyone's using this","If you're seeing this for too long, it's a bad sign.","This might hurt a little.","Please wait... I'm pretending to work.","Close all your programs and run."} rm=math.random(1,#msg) msge=msg[rm] if lastimp then dropstuff=lastuff lok=lastlog zerozz=lastzeros fld=lastfield else dropstuff="replacer" lok=false zerozz="01" fld="effect" end g_impex={"import OP","import ED","import sign","import signs","export sign","import chptrs","update lyrics"} g_stuff={"save/load","replacer","lua calc","jump to next","alpha shift","motion blur","merge tags","add comment","add comment line by line","make comments visible","switch commented/visible","reverse text","reverse words","reverse transforms","fake capitals","format dates","split into letters","explode","dissolve text","duplicate and shift lines","randomized transforms","clone clip","what is the Matrix?","time by frames","honorificslaughterhouse","transform \\k to \\t\\alpha","fix kara tags for fbf lines","convert framerate"} unconfig={ -- Sub -- {x=0,y=16,width=3,height=1,class="label",label="Left "}, {x=3,y=16,width=3,height=1,class="label",label="Right "}, {x=6,y=16,width=3,height=1,class="label",label="Mod "}, {x=0,y=17,width=3,height=1,class="edit",name="rep1",value=sub1}, {x=3,y=17,width=3,height=1,class="edit",name="rep2",value=sub2}, {x=6,y=17,width=3,height=1,class="edit",name="rep3",value=sub3,hint="start/count by[limit]"},

-- import {x=9,y=3,width=2,height=1,class="label",label="Import/Export"}, {x=9,y=4,width=2,height=1,class="dropdown",name="mega",items=g_impex,value="import signs"}, {x=11,y=4,width=1,height=1,class="checkbox",name="keep",label="keep line",value=true,}, {x=9,y=5,width=3,height=1,class="checkbox",name="restr",label="style restriction (lyrics)",value=false,}, {x=9,y=6,width=3,height=1,class="edit",name="rest"},

-- chapters {x=9,y=7,width=1,height=1,class="label",label="Chapters"}, {x=10,y=7,width=2,height=1,class="checkbox",name="intro",label="autogenerate \"Intro\"",value=true,}, {x=9,y=8,width=2,height=1,class="label",label="chapter marker:"},

{x=11,y=8,width=1,height=1,class="dropdown",name="marker",items={"actor","effect","comment"},value="actor"}, {x=9,y=9,width=2,height=1,class="label",label="chapter name:"}, {x=11,y=9,width=1,height=1,class="dropdown",name="nam",items={"comment","effect"},value="comment"}, {x=9,y=10,width=2,height=1,class="label",label="filename from:"}, {x=11,y=10,width=1,height=1,class="dropdown",name="sav",items={"script","video"},value="script"}, {x=9,y=11,width=2,height=1,class="checkbox",name="chmark",label="chapter mark:",value=false,hint="just sets the marker. no xml."}, {x=11,y=11,width=1,height=1,class="dropdown",name="chap",items={"Intro","OP","Part A","Part B","Part C","ED","Preview"},value="OP"}, {x=9,y=12,width=3,height=1,class="edit",name="lang"},

-- numbers {x=9,y=13,width=2,height=1,class="label",label="Numbers"}, {x=9,y=14,width=2,height=1,class="dropdown",name="modzero",items={"number lines","add to marker"},value="number lines"}, {x=11,y=14,width=1,height=1,class="dropdown",name="zeros",items={"1","01","001","0001"},value=zerozz},

{x=9,y=15,width=2,height=1,class="dropdown",name="field",items={"actor","effect","layer","style","text"},value=fld} ,

-- stuff {x=0,y=15,width=1,height=1,class="label",label="Stuff "}, {x=1,y=15,width=2,height=1,class="dropdown",name="stuff",items=g_stuff,value=dropstuff}, --dropstuff {x=3,y=15,width=1,height=1,class="dropdown",name="regex",items={"lua patterns","perl regexp"},value="perl regexp"}, {x=4,y=15,width=1,height=1,class="checkbox",name="log",label="log",value=lok,hint="replacers"}, {x=8,y=15,width=1,height=1,class="label",label="Marker:"},

-- textboxes {x=0,y=0,width=9,height=15,class="textbox",name="dat",value=data}, {x=9,y=1,width=3,height=1,class="label",label=" Selected Lines: "..#sel},

-- help {x=9,y=0,width=3,height=1,class="dropdown",name="help", items={"--- Help menu ---","Import/Export","Update Lyrics","Do Stuff","Numbers","Chapters"},value="--- Help menu ---"}, {x=9,y=17,width=3,height=1,class="label",label=" Unimportant version: "..script_version}, } loadconfig() repeat if pressed=="Help" then aegisub.progress.title("Loading Help") aegisub.progress.task("RTFM") if res.help=="Import/Export" then help=help_i end if res.help=="Update Lyrics" then help=help_u end if res.help=="Do Stuff" then help=help_d end if res.help=="Numbers" then help=help_n end if res.help=="Chapters" then help=help_c end if res.help=="--- Help menu ---" then help="Choose something from the menu, dumbass -->" end for key,val in ipairs(unconfig) do if val.name=="dat" then val.value=help end end end if pressed=="Info" then aegisub.progress.title("Gathering Info") aegisub.progress.task("...") info(subs,sel,act) for key,val in ipairs(unconfig) do if val.name=="dat" then val.value=infodump end end end pressed,res=ADD(unconfig, {"Import/Export","Do Stuff","Numbers","Chapters","Repeat Last","Info","Help","Save Config","Cancel"},{ok='Import/Export',cancel='Cancel'}) until pressed~="Help" and pressed~="Info" if pressed=="Cancel" then ak() end lastimp=true lastuff=res.stuff lastlog=res.log lastzeros=res.zeros lastfield=res.field if pressed=="Repeat Last" then if not lastres then ak() end pressed=lastP res=lastres end ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame progress("Doing Stuff") aegisub.progress.task(msge) sub1=res.rep1 sub2=res.rep2 sub3=res.rep3 zer=res.zeros if pressed=="Import/Export" then important(subs,sel,act) end if pressed=="Numbers" then numbers(subs,sel) end if pressed=="Chapters" then chopters(subs,sel) end if pressed=="Do Stuff" then if res.stuff=="jump to next" then sel=nextsel(subs,sel) elseif res.stuff=="convert framerate" then framerate(subs) elseif res.stuff=="alpha shift" then alfashift(subs,sel) elseif res.stuff=="merge tags" then sel=merge(subs,sel) elseif res.stuff=="honorificslaughterhouse" then honorifix(subs,sel) else stuff(subs,sel) end end lastP=pressed lastres=res if pressed=="Save Config" then saveconfig() end

aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, unimportant) How to Use xdelta Files

It's really simple. Let's say you have episode01v2.xdelta and need to patch your episode.

1. Download xdelta3.exe from somewhere. 2. Create patch.bat (or whatever name, but .bat). 3. Type "xdelta3.exe -d episode01v2.xdelta" in it (without the quotation marks) and save it. 4. Double-click on the .bat file. Done.

The files have to be in the same folder as the file you're patching. Alternatively, just put xdelta3.exe in your system folder and keep it there for future use. Or put it wherever you want and include full path in the .bat file.

If, by any chance, you want to create an xdelta, you need a command line like this:

xdelta3 -s "episode01.mkv" "episode01v2.mkv" episode01v2.xdelta

(Quotation marks only really needed for whatever has spaces in it.)

How to Use xdelta Files without Needing .bat Files

If you want to make things easier and patch files just by double-clicking the xdelta file, you need to edit the registry.

Find these entries in regedit:

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.xdelta

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\xdelta_auto_file\shell\open\command

This is from WinXP, and it should be the same in win7 and 8. If it somehow isn't, search for "xdelta" under HKEY_LOCAL_MACHINE, and you'll figure it out.

Under these entries, you will probably see a String Value with the name "(Default)". If not, create it. Then double-click it to edit and put this under "Value data":

"xdelta_auto_file"

"C:\WINDOWS\system32\xdelta3.exe" -d "%1"

First one's for the .xdelta, second for the shell\open\command.

Again, the path here will be to wherever your xdelta3.exe is. When you double-click an xdelta file now, it should apply the patch automatically.

Alternatively, download this. It contains a .reg file. Running it (just double-click) will add those 2 entries to the registry, and stuff should start working.

If the link dies or something, you can create xdelta.reg (or whatever name, but .reg) and put this in it:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.xdelta] @="xdelta_auto_file"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\xdelta_auto_file\shell\open\command] @="\"C:\\WINDOWS\\system32\\xdelta3.exe\" -d \"%1\""

If you need a different path, mind the double backslashes in the path. General Tips for Using Aegisub

Some settings you should probably change to these [updated for 3.0]:

• General - might be useful to raise the undo levels, especially for typesetting

• Audio settings should look something like this:

Make sure to enable "Snap markers" "Show all" inactive lines is useful especially for timing.

• Video - set default width/height to 1280/720 as that's what we use 99% of the time.

The 2nd checkbox is useful for typesetting because sometimes the tools get in the way.

• Hotkeys: change the ones you need so that you can use them more easily

You can also change colors for the subtitle grid and audio display. I find it especially useful for the subtitle grid, as the defaults can be hard to tell apart.

If you're like me, you're gonna reverse the whole thing: [Sometimes I get asked how I changed the main BG color [darkish grey on picture below] - that's windows settings]

Script Resolution [This is important]

When you click that white paper icon up there, you'll see this Script Properties dialogue, where, among a lot of irrelevant things, you have Resolution. This is something you always have to check and always set correctly.

TL;DR - 99% of the time we use 1280x720 so set it to that. Make sure you don't keep 848x480 when using HS scripts. Setting script resolution goes along with importing styles so get used to doing both together. How it works, long explanation: This sets the resolution of the workspace for the subs. Whatever values you use will be 'stretched' to the whole video by the subtitle renderer, and all positioning etc. is calculated from that. If you set 100x100 and put it on a 1280x720 video, letters will be widened and huge, and \pos(50,50) will appear at (640,360) of the video. Or another example, if you set it to 128x72, then every pixel of your script will correspond to 10x10 pixels of the video. Which means you'll have shitty accuracy for positioning. [update: actually letters shouldn't get stretched because only the vertical value is used for resampling but some weird stuff does happen when you change the aspect ratio...]

Using a 480p script for a 720p video basically works [HS has a 480p script for all 3 videos] but you have to understand the problems that may arise from doing that. Firstly, once you set the resolution, you have to do everything in that resolution and never change it. If you change it, all sizes and positions go haywire. Which also means, secondly, that all the styles are closely tied to the resolution. Since we use the same styles [at least the Default/OP/ED] for all episodes of any given show, changing the resolution in a random episode would be a disaster. Also, as I said, with higher resolution you have more accuracy so it's dumb to use lower script resolution than the video is. You could technically use 12800x7200 for even more accuracy, and it would work, but you'd be dealing with a lot of lag.

When do we not use 1280x720? Some things air in lower quality, some OVAs only stream in 480p on the web. Un-Go was encoded in 576p so we used 1024x576 for that show. However... Another [the show] was in 720p, while the OVA was in 576p. Since I was obviously gonna use the styles from the earlier episodes, I certainly didn't feel like changing all the sizes and song styling for a 576p script, so I used 720p script for the OVA, so I could keep all the styles as they were. So we really only use a different script resolution for something that's encoded in that resolution from the beginning.

You'll learn the basics of timing and typesetting elsewhere, so here's just a few useful things that you might miss:

^ The first 4 icons. First 2 are self-explanatory, second 2 for frame timing are explained in Typesetting Basics.

The icon under View. Useful for shifting. Select lines you want to shift, search in video for the frame where you want the first current one to start, click the icon.

The S under Help is the Styles Manager, the next one is where you set Script Resolution [described above].

The one after the human head is for more shifting options, the clock icon opens the TPP.

The one between the fish and clock is Resample Resolution... DON'T confuse it with the Script Properties and don't fucking use it!

One before last is Options, the last one changes how tags are displayed in the script [try it on a typeset script]. [I suggest displaying whole tags, expecially for typesetters]

Check the Comment to make a line not show on screen. Use this if you time signs but won't be typesetting them. The typesetter can find them more easily that way and in case of some fail like "oh, did we forget to typeset this?" the unstyled signs won't show up in the release.

The drop box lets you change style for the current or all selected lines.

Actor field is useless. You can delete whatever's there, if it annoys you. 1 is for layers. You'll need that for typesetting, usage explained in Layers. If you're a timer only, just remember this is where it is in case you ever need it.

2, 3, 4 you normally don't need as you use either audio track or the blue icons for timing. But you can type2edit.

5,6,7 = left/right/vertical margin. Overrides default values set in Style. Lets you do it easily without adding tags. Useful for default lines when you want to avoid overlapping with other things on screen.

8 = obviously bold, italics and stuff. In case you were looking for those.

9 is where you can change the font for the current line.

10 is where you change colors for the current line.

Commit doesn't exist anymore, thank god [old screenshot].

k thx bye Scripts for Aegisub

A bunch of scripts that don't come with aegisub by default:

» All of my own scripts can be found here. (GitHub repository here.)

» Here you can find links to scripts from lyger, torque, line0, and others

Aegisub-Motion | Latest version here

Script for motion tracking with Mocha.

This is roughly what things should look like. Set the folders in the config (below) as implied.

The 'Trim' window doesn't exist anymore. ->

If you copy data from Mocha, they will be pasted automatically in the top part of the 'Apply' dialogue. -> Duplicate and shift by 1 Frame backwards

If you don't know what this is, then you don't need it.

Bezier

Aligns text along a bezier curve. Won't do shit if you don't read the instructions in the script so please...

BT.601 -> BT.709 Color Converter

BT.709 -> BT.601 Color Converter

Daiz's scripts to fix the mess he created in the first place. (This is not needed in newer builds that do it with resample dialog.)

How to use scripts

Just place the lua in Aegisub's automation/autoload folder. It will load when you start Aegisub unless it's somehow corrupt. If you modify the script with Aegisub open, go to "Automation -> Automation" in the menu and click "Rescan Autoload Dir." That will reload all scripts in the autoload directory.

If you download a script and it's not loading in Aegisub, it might be because you're using Chrome and it's adding some html crap to it, so make sure to avoid that.

Bind your scripts to hotkeys.

Go to Preferences-Interface-Hotkeys, add a new line under Default. Set a hotkey and under "Command" type "auto". Aegisub will show you a list of loaded automation scripts. Select the one you want.

Write your own scripts!

If you still need more functions, learn how to write lua scripts for aegisub here. If you're familiar with any programming language, it will probably be easy. If not, you can still learn to do so with this guide. [Even I did, so it can't be that hard.]

« Back to Typesetting Main How Do I into IRC? Written by Orcus

So you've decided to come on IRC. Good for you. Good for you.

First thing you need to do is install a client. Easiest client for newbies is mIRC. Obtain from here.

Now, mIRC gives you a 30-day evaluation period. After which, you are delayed a whole extra 10 seconds on start-up. If those 10 seconds are precious and irreplaceble to you, then you have serious problems and are probably willing to pay the fee.

Secondly, you need to install and start mIRC up. You'll get the obligatory "Please send me money" dialogue. Just click continue (get used to doing this). You should now be at the connect screen. This is where you enter your main nick in the Nickname box. The Alternative is the nick mIRC will connect under should you unexpectedly disconnect. Because you are hopefully not autistic, you will leave the optional email and real name boxes blank.

Before you click connect, you will want to select the server first. Go down to Servers in the expanded Connect menu and scroll down to Rizon. Double click and highlight random server, then click select. If all went well, you should be back at the first screen (with your nick and alternative).

NOW PRESS CONNECT.

After the IRC churn you should be connected to Rizon! The window you are seeing is called the console window. Do not connect to a channel now; if a channel list pops up, click Cancel.

Now on to registering your nick.

If the nick is registered you should see a message say as much. In this case, you have 60 seconds to change your nick to something else or the server will do it for you (you will become a GuestNUMBERS). If you don't see this you may register the nick to nickserv. For this you must have an email (but who doesn't these days?).

An important and manifoldly useful command is /ns help - try it now. Note that /ns is the equivalent of /msg nickserv so you can replace any instance of /msg nickserv with /ns.

The command you want is /ns register PASSWORD EMAILADDRESS (type /ns help register for more information). Remember DO ALL OF THIS IN THE CONSOLE WINDOW. I've seen people fuck up in channel windows and have broadcast their email and nick password to 400 fucking people. NEVER EVER TYPE SENSITIVE INFORMATION INTO A FUCKING CHANNEL WINDOW. EVEN IF YOU ARE THE ONLY ONE IN A CHANNEL. If you have followed the guide so far, you would not even be seeing a channel window.

Anyway, check your emails (check your SPAM box too, because Rizon signs itself up to numerous spam sites). Do what the email says and you should be registered. Before you go any further, it pays to check you are registered. In the Console window type /whois YOURNICK where your replace YOURNICK with your nick (yes, some people have to be told this). If you see a line YOURNICK's info: has identified for this nick then you are registered. Congratulations. The first hurdle is over.

Now it's time for some fancy automation. Press Alt-O (O for Orgasm) for the Options menu. You should should see a list of tickboxes. Make sure Use Invisible Mode is ticked.

Click the Perform button on the right hand side. This will bring up another dialogue box. Click Enable Perform on Connect at the top and then click the Add button. Scroll down to Rizon and click Ok.

Now, in the space Perform commands you want the following lines (you can add more yourself later, but these are the useful ones)

/ns identify YOURPASSWORD (if you type YOURPASSWORD literally, disconnect now. Please.) /mode YOURNICK +x (this cloaks your hostname. Most clients do this automatically, but it pays to be sure) /ns set kill quick (stops people using your nick) /ns set private on (stops people finding your nick)

Later you will (yourself) learn how to be un-herkz-like and have a vhost. After which, you can add /hs on to the perform list.

Click Ok to get back to the Console window. Press Alt-J to bring up the Favourites list. This is where you will set up channels to autojoin on connect. For a start, check Enable join on Connect at the bottom, then click the Add button. In the Add Channel window, type in #commie-subs into the channel dialogue box Check both the boxes in the options then click OK (don't worry about changing the Networks list because if you know about other networks then why the fuck are you reading this?). Click OK to get back to the console window.

Now type /quit and then click the Lightning Bolt button at the top left of the screen to reconnect. If everything went well, you should automatically identify and join #commie-subs.

More helpful advice:

Alt-O Go to IRC Decheck Auto-join Channel on invite Check Rejoin Channel when Kicked (to amuse the OPs) Check Rejoin Channels on connect Check Minimize Query Window Go to Messages Check Timestamp Events Put [HH:nn:ss] in the box Check UTF-8 encode/decode messages Check Split long channel/query messages Go to Flood Check Enable Flood Protection Check all options in Limit

Go to DCC Make Sure Show get dialog is selected Go to folders and set the appropriate shit up Got to Server and disable that shit

Go to Display Check Always Highlight on message

Click Ok to get out of that

Pres Alt-B to bring up the address book Go to Highlight tab Check Enable Highlight Click Add button In Highlight lines that contain these words type $me (yes with the dollar sign) Under colour select Red (or something, but red is noticable.) Select a sound if you want Enable Flash and Tips if you want. Click OK Now when people use your nick. The message will show up as red with flashing, sounds and tips etc. IRC basics & Using KVIrc by unanimated

I'm not really that great with IRC but after a year of using it I suppose I should be able to lay out the basics for beginners.

Q: Why do I even need IRC at all? I don't like it. A: Because you can't fansub without it.

Q: Is using IRC difficult? A: Fortunately you only really need the basics, which you'll learn the first day, hopefully from this guide.

All in all you need just a few things: 1. choose a nickname 2. connect to rizon 3. register your nickname 4. join channels 5. automate identification and joining channels 6. set character encoding to unicode

There are a lot of things you can do after that but this is pretty much all that's really necessary.

First you need a nickname, so go to Settings - Configure KVIrc...

...and type your nickname in here. The 2nd field seems pretty useless in KVIrc, what you put in the 3rd is visible when users hover mouse over your nickname to get some more info. [This can be helpful when people change their nicknames - many users switch between several nicknames all the time.]

Now go to KVIrc - New Connection... and you'll get here: This is what you want to connect to - Rizon, ie the server where pretty much all fansubbing takes place.

When you connect, you'll see some server info etc. You are now connected to the Rizon server, but not really to any people yet. You'll be using this Rizon window to type commands, because if you type them in a channel and screw up, everyone may see your password or whatever you type. What you send to this Rizon window, nobody sees.

REGISTERING YOUR NICKNAME

What you should do first is to register your nickname, for hopefully understandable reasons. If your nickname is already registered by someone else, Rizon will tell you so and change your nickname to Guest123568. In such a case you need to choose a different nick. To register you need to communicate with NickServ, using the command /ns, which is short for /msg nickserv. Type /ns help to get the idea of how it works. You'll see a bunch of commands you can use by typing /ns [command]. To get help on a specific command, you type /ns help [command]. You need to register, so /ns help register [no need for upprecase]. Among other things it will tell you this: Syntax: REGISTER password [email]

The syntax is what comes after /ns. This is how the whole command system works, basically, and you can use the pattern for other things than nickserv later. So now you need /ns register [password] [email]. Email must be valid, as it sends you a confirmation form.

When you confirm the registration, you need to identify yourself for the nickname you've chosen. This is done with /ns identify [password]. With this Rizon recognizes you as the person registered with that nickname.

Now you can move on to joining channels. This is pretty simple - you just type /join #channel_name, for example /join #commie-subs If a channel has a password, it's /join #channel_name channel_password

AUTOMATING IDENTIFICATION AND JOINING CHANNELS

Since you need to do this every time you connect, you want to automate it. This is how you do that:

Go to "Scripting" -> "Edit Events...", right-click the "OnNickServNotice" event, click on "New Handler" in the popup menu, and paste the code below.

// Identify and auto-join on success. if ( $str.contains($3, "This nickname is registered and protected.") && $context.networkName == "Rizon" ) { timer -s (identify, 2000, $0) { msg -q $0 "IDENTIFY password"; // Edit this line. } } elseif ( $str.contains($3, "Password accepted - you are now recognized.") && $context.networkName == "Rizon" ) { timer -s (joinchannels, 2000) { // Edit this block. join #channel-1; join #channel-2 channelpassword; join #channel-3; } }

^ Edit the code you just pasted to use your password and the channels you want to join automatically.

SETTING UNICODE

Now you just need to set character encoding to unicode, otherwise you'll be seeing a lot of garbage. Go to Settings - Configure KVIrc... - General options - Language, and set Default text encoding to UTF-8.

Now you have everything important set up, the rest is just for convenience.

SENDING PRIVATE MESSAGES

Type /query [nickname] and then type in the window that opens, or right click on the user in userlist and select Query.

HIGLIGHTING

KVIrc will highlight you by default if anyone types your name, so that will do for starters. To add more things to your highlights, go to IRC -> Tools -> Alert/Highlight in General Preferences. One derp in KVIrc is that it doesn't highlight your nick with a question mark after it, so if your nick is for example animoron, add animoron? to your highlights.

If you use more nicknames, add all of them so that people can highlight you even with those you're not currently using.

You can add anything else you want to be notified about. If the staff channel announces new encodes with "File added - horizon01_premux.mkv" and you're working on that show, add File added - horizon to your highlights - that will ping you for everything that contains that pattern, ie all other episodes.

Check the stuff about color settings further below for how to change the color of highlighted messages.

GETTING A VHOST

Full username on IRC looks like this: Athena [[email protected]] This equals nickname [username@vhost]. Nickname is obvious. For some reason KVIrc doesn't really use what you set in Username but instead uses this Ident thing [possibly depends on settings, not really sure], which by default says "kvirc". In Preferences you can go to Connection - Advanced - Ident Service and change it in the "Ident username" field [needs restart].

Now for the vhost part, you can get pretty much anything you want there, just for the purpose of looking more interesting than random numbers or whatever you get by default. You'll use this thing called HostServ, which works similarly to NickServ. [/hs help] Basically you type /hs request how.does.this.irc.thing.work to request said vhost. You need periods between the words and there's some length limit and basic rules, like no racist crap etc. Then you wait till an OP sends you a memo saying your vhost was approved [may take minutes to hours]. Switch it on with /hs on or /hs update.

MULTIPLE NICKNAMES / VHOSTS

You can use multiple nicknames that will all identify you as the same person. Type /nick whatever to change your nickname to whatever. Then /ns group your_registered_nick password. This will group this new nick with the old one and they'll both work the same for you from now on. If you disconnect and reconnect with your alternate nickname, if they're grouped you won't have to identify yourself again. If you request a vhost while using your other nickname, the new vhost will be assigned to that nickname. If you switch nickname, you need /hs update to switch the vhost as well, otherwise it just stays as whatever you have at the moment, until you reconnect to Rizon.

Similarly to nicknames you can group the vhost to apply to all your nicks. /hs help group for details.

RANDOM KVIRC SETTINGS

Here's a few things you might wanna check/change in the General Preferences. In the Connection section, have everything checked. In IRC - Channel, have both those "On Channel Kick" things checked. In IRC - Tools - Logging, you can set what you want to log, obviously. Logs are always useful, imo. If you want to browse them, don't open them in notepad, go to Tools - Browse Log Files. This inbuilt thing includes formatting - in notepad you'd see a chaotic mess. In Interface, check Disable splash screen. In General Options - URL handlers, you can change the way to open links to Signle click. In Interface - Notifier, you can change some things about that notifier box that pops up when you get highlighted.

When you have time you can check all the other stuff but most of it is pretty irrelevant.

AUTOMATIC UNBANNING

You can unban yourself from channels you're registered on with /cs unban [channel]. To save yourself the trouble of typing stuff you can add these lines to the Event Editor:

Put this into OnMeKick:

timer -s ("kickdelay",3000,$chan.name,$chan.key) { join $0 $1; } And this into OnMeBan:

cs unban $target;

If you use this, you have to disable "Rejoin channel" in Preferences -> IRC -> Channel.

HOW TO MAKE KVIRC NOT LOOK LIKE SHIT

Not that KVIrc looks worse than other clients... they all look like shit. But with KVIrc you can actually change that. If you're gonna fansub, you'll be looking at your client every day, and if you're gonna do that, you might as well change the appearance so that it doesn't give you eyecancer.

Go to Settings - Configure Theme...

Input relates to the area where you type messages. Set colors, font, etc.

Output is for the chat window [including pm, console window etc.]. In the Features tab you might wanna increase the backlog size. If you wake up or come home from work, you need to be able to scroll up and read your highlights, and sometimes people talk a lot so the default backlog size might only show a portion of what was going on. So set Maximum buffer size to something higher. I use 5000, which is good enough for staff channel, but the niggers in the main channel are capable of filling that in a few hours. Not that you really need to care about the main channel.

This part here is about what all the messages will look like: I don't remember the defaults but I think I turned off some stupid shit here to avoid eyecancer. Certainly don't use "Smart" nickname colors because that's incredibly retarded and will give you eyecancer for sure.

"Use same colors as in the userlist" is handy, because in the Userlist section you set different colors for OPs, Owners etc. and this settings lets you distinguish between those in the chat as well.

The color set is where you define colors you wanna be using. Here is where you assign the colors to each type of message:

Might wanna set the Highlight color to something that will be clearly noticeable but not annoying. Mainly it should be a different color from everything else so that you can find your highlights easily just by scrolling through the backlog.

Obviously you don't have to set colors for everything. I don't even know what most of those things are.

Things that might be useful are:

- the first 5 - Join, Part, Quit - Kick, Ban, Unban - Topic, nick - Server info, MOTD, NickServ, ChanServ, QueryNotice, ChannelNotice, ServerError

You can chek the Timestamp tab if you don't like what your timestamps look like.

Notifier is the thing that pops up on your highlights, so color settings are there.

Userlist is pretty clear. In Foreground you can set colors for OPs etc.

In Window List check Use tree window list.

In Tree Window List you can set colors to different types of messages in the chat. Level 5 is the color you'll see if someone highlights you in that channel. Level 4 is when somebody speaks. Level 3 and below is pretty irrelevant stuff - join/part messages etc.

That's about it for the themes.

That green arrow above the userlist expands a little menu where you can click on the Split View icon to separate join/part messages from the actual chat. This is useful in large channels with too many people joining/leaving, as all those joins/parts make the chat too chaotic. You can then make the non-chat part as small as possible as it's pretty useless.

In staff channels this isn't an issue and it's actually useful to see who's joining or who dropped out.

Colors for specific users

As you can see above and below, it's even possible to assign any color to any user. Right-click on a user in the userlist...... and click Registration -> Register [it shows Edit Registration here because Athena is already registered] This "Registration" refers to a database of users you can make to specify some things for them, like colors or ignoring. Just click on OK, or Next or whatever to get through the process. Then go to Settings -> Configure Registered users... You'll see a list with the users you've registered. Double click on one and...

...you'll get this.

The registration you did adds the whole usernema+hostname string, by which it identifies the user.

Some users change heir nickname/vhost often, so to make sure this thing can recognize them you can either Add all the versions here, or Edit the first one to make it include all of them. For example this edited mask here will match Athena by nick, even if username and hostname change. So you just have to use whatever tends to not change for any given user. Usually users change nick and vhost and the middle part stays the same, so that might be the safest way. Here that would be Atan, but sometimes for some users this part changes between Atan and ~Atan [example] so you might need to set it to *Atan. In this case, however, that would include Satan, which might very well be another user, so you have to think a little. In most cases it's fairly straightforward though.

Once you set the mask[s], you can go to the Properties tab, check "Use custom color in userlist" and set the color. Add as many users as you want. It takes some time for KVIrc to update this. It either gets updated when the user speaks, or after a few minutes maybe. When that happens, you'll see the colors in the userlist, like you see here on the right, and also in the chat, as long as you have the "Use same colors as in the userlist" option checked.

As you may have noticed, you can also use the Ignore tab to ignore annoying users.

That's all, I guess.

Below you can see examples of what the chat may look like when you set colors for most users in the staff channel.

Alpha Timing

Alpha Timing is a practice used to make some parts of text appear later than others. Some groups started to use it for dialogue lines that had a pause in them.

For example a line like "Are you... serious?" would have a pause in the middle. With regular timing you can read the rest of the line before the character says it, which can sometines reduce dramatic effect, so you may want to avoid it, and splitting this into 2 lines might be a bit too weird.

So what you do is make the "serious" part appear only when the character says it, while the start of the sentence stays in the same place. The way to do it is this:

Dialogue: 0,0:00:00.00,0:00:01.00,Default,,0,0,0,,Are you... {\alpha&HFF&}serious? Dialogue: 0,0:00:01.00,0:00:02.00,Default,,0,0,0,,Are you... serious?

First line timed to first second, second to the second (no puns intended). Basically it makes the latter part of the line invisible, but since it's technically there, you don't have to change the positioning of the line.

Sometimes this is also useful for typesetting, like when a period or an exclamation mark appear later than the rest of the text.

Of course this also works with more than just 2 segments:

Dialogue: 0,0:00:00.00,0:00:01.00,Default,,0,0,0,,Weird{\alpha&HFF&}... Dialogue: 0,0:00:01.00,0:00:02.00,Default,,0,0,0,,Weird.{\alpha&HFF&}.. Dialogue: 0,0:00:01.98,0:00:02.98,Default,,0,0,0,,Weird..{\alpha&HFF&}. Dialogue: 0,0:00:02.98,0:00:03.98,Default,,0,0,0,,Weird...

Be aware that a lot of people hate alpha timing for dialogue, mainly because some groups, like Lunar, abused the shit out of it and used it almost like karaoke. This is how to encode a workraw in 2 [or more] threads without needing any scripts or avisynth

• Create wr2.bat, type in:

x264 --video-filter resize:640,360,method=bilinear --qp 35 --preset ultrafast --scenecut 98 --min-keyint 1 -- keyint 600 --seek %2 --frames 20000 -o %1_wr%2.mkv %1

• Create wraw.bat, type in:

start wr2 %1_premux.mkv 0 start wr2 %1_premux.mkv 20000 @pause start /wait mkvmerge -o %1_wrv.mkv -A %1_premux.mkv_wr0.mkv -A +%1_premux.mkv_wr20000.mkv

• Have wr2.bat, wraw.bat, x264.exe [8-bit] and ??????_premux.mkv in the same folder

• open cmd.exe, type "wraw ??????"

2 encoding processes will start, first window will show "press any key to continue..."

• wait for both processes to finish & then press any key in the first window. done.

?????? is the episode name/number in standard Commie premux naming, for example for shana20_premux.mkw you type wraw shana20 I do this to avoid having to type/copypaste the _premux.mkv part this can, of course, be modified to suit your needs [if you understand how it works]

______

How it works... wr2.bat says "encode 20000 frames of this premux, starting from a specified frame" wraw.bat says "use wr2.bat to encode premux starting on frame 0, same starting on frame 20000; merge them with mkvmerge"

2 encoding processes start simultaneously, one encodes 20000 frames from frame 0, the other from frame 20000 an episode usually has somewhere between 35000 and 38000 frames, so this is designed for that length you could probably do well with the split point at 19000 instead of 20000, this is just in case encoding speed totally depends on your CPU, but just for comparison... my regular wr settings take 10 minutes to encode. the fastest settings [the ones used here but in 1 thread] get it down to 8 minutes. with 2 threads it's between 5 and 6 minutes. 1 thread alone uses about 60% of my CPU, which is why 2 threads don't double the speed. if 1 thread takes 90% of your CPU, it's of little use to run 2 threads. [your PC just sucks. deal with it.] if it's under 40%, you can make use of more than 2 threads. for example if you want to use 4 threads, you'll modify like this: in wr2.bat change --frames 20000 to --frames 10000

wraw.bat will be:

start wr2 %1_premux.mkv 0 start wr2 %1_premux.mkv 10000 start wr2 %1_premux.mkv 20000 start wr2 %1_premux.mkv 30000 @pause start /wait mkvmerge -o %1_wrv.mkv -A %1_premux.mkv_wr0.mkv -A +%1_premux.mkv_wr10000.mkv -A %1_premux.mkv_wr20000.mkv -A +%1_premux.mkv_wr30000.mkv

if you need to work with differently named premuxes, change the first lines in wraw.bat to "start wr2 %1 0" etc, mkvmerge lines to "-A %1_wr0.mkv", and use "wraw wholefilename.mkv" as a command line [in other words remove all _premux.mkv from wraw.bat] [don't change anything in wr2.bat] [if there are spaces in the filename, you have to use quotation marks: wraw "whole file name.mkv"]

I hope this is detailed enough so that even people more technically challenged than me can get it Introduction

This guide was written mostly in 2011-2013, with only a few updates after that, and therefore is obsolete in many ways. I don't care about anime or fansubbing anymore, so I'm not planning on keeping it updated, aside from possibly minor additions that someone basically hands over to me and I just add them.

Things were very different in 2011 when the first chapters were written. There were (almost) no automation scripts, only a handful of people knew how to use mocha, nobody was using xy-vsfilter yet, and everything lagged like shit. Much of this guide still reflects those times, so you shouldn't necessarily take every advice you find here. For example typing any tags instead of using automation would be pretty stupid today and would cost you a lot of time. There are scripts for almost everything, written by me and others.

It should also be noted that nobody gave a shit about libass when this guide was written, so everything is written assuming that you're using vsfilter and nobody will watch it with libass. Recently, though, libass has been evolving probably faster than vsfilter and thus become much more relevant, so typesetters may want to consider making sure their signs work the same on both renderers. For most things, this is not a problem, but there are some issues that each renderer handles differently. I don't really know what they are, as I'm mostly out of the game, but I know some of them concern rendering fonts.

If you want to typeset seriously and want to have up-to-date information, I highly suggest joining #irrational-typesetting-wizardry on rizon. There you can talk to a lot of people who can help you with all kinds of problems, as well as directly with authors of commonly used scripts, Aegisub/vsfilter/libass developers, etc.

You can find a lot of stuff on this github link. It includes my scripts, lyger's, torque's, line0's, Youka's, etc. You can find all these people in that irc channel I mentioned, as well as Plorkyeran and jfs who work on Aegisub, and rcombs who works on libass. At least that's the situation at the time of writing this, February 2015.

What has changed since the guide was written?

Things lag much less, so some warnings about lag may not be relevant anymore. Today you can have many transforms in one line, track several signs at once, or make huge gradients without any lag. It's usually only if you combine too many of these things that things get a bit slow. If there are two methods to do a sign, it's not always easy to tell which one will be less laggy, so sometimes you just have to try.

Mocha tracking is a must, and if you want to typeset decently, you definitely have to learn that.

Vector drawings have become very common for masking and other purposes, and there are tools to create them easily. Don't use stupid shit like FansubBlock.ttf.

Some of my own examples in this guide, while they may have looked great in 2012, don't look so good now. A lot of them were made without any scripts and with incomplete understanding of all available tools, since even when I was writing the guide, I was still learning myself.

Aegisub has evolved a lot since 2011. Some things in this guide regarding the program may be obsolete.

It's not uncommon to do one sign in 5 layers or more. If it's relatively easy to do and makes the sign look better, go for it. It's unlikely to lag even when mocha-tracked.

While I haven't gone in that direction myself (I only use Aegisub, ASSDraw, and Mocha), it's pretty common to use other software, like Illustrator. Check line0's and torque's stuff for tools and guidelines for that.

Sorting by time is not needed anymore. Only old vsfilter had that bug, and both xy-vsfilter and libass have no problem with it.

Some general notes about typesetting

There is no single, universal goal of typesetting. Different typesetters have different methods and goals. So don't really let people tell you how you "should" do it. Typesetting is largely about the ratio of time and effort you put into it and the quality of what comes out. What you want this ratio to be is up to you. You may want the best results, or you may want it done fast, or you may want something in between. Hdr was a typesetter who usually went for the best possible quality, at the cost of endless time spent on it and often breaking people's computers with massive lag. Examples of those who go for speed over quality are of course many. My goal has always been to find a reasonable ratio. There were certainly times when I went for higher quality than today, but I've (almost) always refrained from spending too much time making only little improvements, especially for things that people normally watching the video (without pausing) would hardly even notice. Of course, you will always find people who will criticize you for that, but fuck them. They usually don't know shit about typesetting anyway.

You should never listen to typesetting advice/criticism from people who don't typeset, and you should never listen to ANY advice/criticism about anything from Dark_Sage and Kristen.

There's no rule saying that you have to typeset every single sign you can find in the video. It's really up to you and your group to decide which signs you should or want to do.

I would also like to mention a few things about matching the original text with your TS. While you should definitely learn to do that and keep it as a general guideline, I don't think it's necessary to follow it as a rule set in stone. Sometimes a sign typeset differently from the original may look better than one that imitates the original as much as possible. This can have various reasons. For example, there may not be enough space to do the same in the same way the original looks, and trying the same look in smaller size could look really bad (especially with borders and shadows), so making a smaller typeset in a completely different font, possibly without or with fewer borders/shadows, may be a decent solution. What really matters is whether it looks good and the viewer doesn't stop and think "WTF is that?" If you pay attention to cases where Japanese studios add an English sign alongside the Japanese one themselves, you will find that the English one often looks very different from the Japanese. If they don't deem it necessary to match the two perfectly, you shouldn't really have to either. But it has to blend in somehow. I have recently gone more in the direction of not matching things that much. I prefer my typesetting work to be creative rather than just tedious slavery following exact rules. If you make a sign that looks good and someone who doesn't typeset complains that you didn't match the original and you should have, just tell them to fuck off, and don't waste your time on arguing with them.

This may be even more relevant with episode titles. Episode titles are usually not a part of the "picture". They're added text, with little to no graphical connection to the video. So for starters, I see little reason why you should place your typeset right under or above the original. You can place it at the opposite side of the screen, or whatever happens to be convenient or look good. As for matching the look, it will obviously look weird in most cases if you just pick a completely different font, but that also depends on the font. English never looks like Japanese, and English text that matches the Japanese font often looks a lot more "boring". So using a font that doesn't match 100% but looks nicer can be a good solution for titles. I also never understood why people try to acquire the actual Japanese font and use the Latin part of it for TS. This to me is mechanical, robotic stupidity. Firstly, just because it's in the same ttf file doesn't mean the English actually looks like the Japanese, and secondly, most of those I've seen could easily be replaced by hundreds of similar fonts, saving you from using a 10 MB font and/or from looking for the exact Japanese font. It's of course up to you, though, how much time you want to spend on pointless shit like that. Then again, not everyone is creative, and some people just like to imitate and match things blindly because they don't really have a mind of their own.

Consistency. Yeah, there's that. While it's certainly nice if you can make repeating signs consistent, the fact is that the more I typeset, the more I notice that Japanese studios are inconsistent as fuck, and making a consistent typeset may often be not only difficult but virtually impossible. So now that I don't really care so much, I just do whatever works instead of wasting time trying to do the impossible. If the studio is entitled to inconsistency, then so am I.

Instead of rewriting things, I have recently added some notes in this colour reflecting on what has changed since that particular part of the guide in question was written. For some reason I originally wrote the guide using American English, but now I'm way too used to both British spelling and punctuation, so there are some inconsistencies regarding that, in case you notice and wonder about that.

« Back to Typesetting Main Typesetting: Creating Styles

The first thing you'll want to do before you start typesetting is to create some styles. You do that in the Styles Manager / Styles Editor, which looks like this:

First you need a Default style, which will be used for all the regular lines. It already exists when you open Aegisub, but looks terrible, so you'll modify it. First you need to choose a font. This is an important choice, because you'll be using the font for all episodes of the show and people will have to be able to read it. So you need one that is easy to read, doesn't have any distracting, unnecessary decorative elements, and isn't ugly. It should also have real italics and should contain all necessary punctuation characters [.,!?-—'"].

Next you need a reasonable font size. That is related to script resolution, which will usually be 1280x720. Even if you were in a group that does 720p and 480p, I'd still recommend making scripts in 720, since it will work for 480p just the same and lets you have more detail in almost everything. To determine what the right size is, you can use a long line from an existing script, and just see how it looks with the size you want. It shouldn't be too big so that you don't get 3-liners, and it shouldn't be too small 'cause then you can't read it. (duh)

Depending on what kind of font you choose, you may want to check the box for Bold, since some fonts look bad in regular mode but good in bold. You will not use Italic for the default font, because that's dumb, and you will most certainly not use Underline and Strikeout, because that's even a lot dumber and only jdp does that.

Then you have the colors. Primary color is obviously the main one. I strongly suggest you don't use anything other than white for the default font. You may think something else looks cool, but it won't seem so cool when you have to read it for 20 minutes.

Secondary color is mostly just for karaoke, therefore quite irrelevant.

Outline color is for the border. Again, for the default font, use black or something dark.

Shadow color should clearly be black, because things really don't cast green shadows.

Under the colors you see boxes with zeroes where you can set transparency for each of the colors. 0 is fully visible, 255 is fully invisible. Clearly 0 for Primary (and secondary), possibly a low non-zero value for Outline, if you know what you're doing (but recommended 0), and something in the range of 120-200 for the shadow (still talking about default font), because shadow with no transparency will make you look like some retard who learned typesetting in Hadena.

In Aegisub 3.0 and later it looks like this when you click on the color: Transparency is the black/white vertical slider and/or the "Alpha" box with the "200" on the right.

Margins are important as well. I don't suppose I have to explain why it's bad if the margins are either too small or too large. If you actually need that explained, give up on being a typesetter and save us the trouble of trying to teach you. Reasonable left\right margin for 720p is around 80-110, reasonable vertical margin (that means both top and bottom) is around 30.

Alignment is obviously 2 for the default subs. The other ones can be useful for OP/ED.

Outline / border size. For the default font, don't try to invent anything much, it has to be readable before anything else, so no ultra thin lines, and no crazy borders thicker than the main font. 1.5-2.5 will probably work fine, but it depends on the font etc.

Shadow distance. For default font, I don't like using shadow at all, but if so, make it a low value (like 1 or even less) and a lot of transparency. Unlike all the previous values, border and shadow don't have to be whole numbers, so you can use something like 0.6.

Scaling. If your font looks good but is too narrow or wide, you can adjust that here. And again, for the default font, don't do anything silly.

Rotation. After 2 years of fansubbing I can safely say that you will never need this.

Spacing. Increases spaces between letters, obviously. For default style, some low values like 0.5 may be helpful for some fonts.

Encoding... is probably useful if you're making chinese subtitles. Which you're probably not. So whatever.

That takes care of the default font. You will certainly want to have more styles than just the default. You'll need a style for the OP and ED and probably for episode titles. You'll create them by simply clicking on New and then it's back to what we've just gone through... starting with a new name. For OP/ED you can divert from the default values much more - all the colors, border, shadow, size, scaling, spacing... are pretty much without limitation. For the episode title you'll obviously be picking something that resembles the original title on the screen.

After that you can create as many styles as you want for whatever you need to typeset. Some people create one style for typesetting and then override everything with tags for each sign. I think that's pretty silly, gives you extra work, and swarms the script with unnecessary tags. I prefer to create a separate style at least for each font I'm gonna use, since it's a lot more convenient than changing the font with tags. And obviously if there are certain kinds of signs that are used repeatedly throughout the show, it's good to have a style for each of those.

How many styles it's useful to have depends on the amount and type of signs in whatever you're typesetting. Sometimes you'll want 2 styles for the same font. For example one black and one white, or one with border and shadow and one without. If the episode has 20 signs with that font, 10 of them white and 10 black, I'll create 2 styles rather than having only a white one and using the \c&H000000& tag 10 times. To do that, you create one style, then click on Copy, change the name and the one thing you want different, like color. This can be used even for alignments. When you have the same kind of sign 10 times on the left and 10 times on the right, you can have 2 styles rather than typing \an? 10 times. After all switching styles is easy:

One last note. Pay attention to what fonts you're choosing. Remember each font has to be muxed into the mkv, so don't pick 10 MB fonts. Any font, even a fancy one, should be under 1 MB. The only exception is if you need to use kanji. From the list of fonts in Aegisub, avoid those that start with @, avoid those that have Adobe or MS in the name, as those are likely to be huge. « Back to Typesetting Main Typesetting Basics

In order to typeset a sign, you need to time it first. As this needs to be precise, you don't do it on the audio track like with regular lines. Signs need to be frame timed, because if your sign is one frame off, you belong in Hadena. So you rough time the sign first, whether on the audio or by inputing the timecode you got from the translator/typist/editor. Then you go frame by frame with arrow keys till you find the first frame where the sign appears [the relevant line must be selected in the script]. Then you click the first of the blue icons here:

That sets the start time. Use arrow keys to check if you did it right. Then navigate to the last frame the sign is visible on and click the second one. Again try if it's right. The first one sets the time at the start of the visible frame, the second one at the end, so if you use both at the same frame, the sign will be visible on that frame, in other words the duration of the sign will not be zero. This can be used for typesetting signs frame by frame by hand.

Many signs start/end at a keyframe so you can use the audio track for those, for others you'll need this method.

When you've timed your signs, you can begin typesetting. You already know how to create styles, so you'll make one. If you need to override anything, you'll use a few tags like \fs \bord \shad etc. You should know all the basic tags and what they do from here. Border is probably what you'll be changing the most often, maybe the shadow and font size, so you should remember \bord \shad \fs at least. You may also switch between regular and bold quite a bit, but bold/italics have buttons above the typing area so no need to type those.

Let's begin. Here's a simple typeset... This is something that will appear in every episode so you want to set as much as possible in the style. The only tags I add here is \fad and \blur. The font, colors and border are set in the style.

Don't make the mistake of using the default (or any extreme) values!

For example the default has a shadow, but you definitely don't want a shadow here, so make sure you set it to 0. Also don't just assume it's black and white. If you do that, you're going Hadena style. Get the actual color from the japanese sign with the eyedropper tool. Aside from the colors and the border, what will make a difference between a good and bad typeset here is the font choice. So don't just take some basic serif or sans serif font, or something like ComicSans, but find something that will actually match and look good. Speaking of which, you need to know your fonts, and you need to have them in the first place.

Now about that fade... Use arrow keys to go frame by frame and find the place where the fade of the jp sign ends. ^ Check the numbers here. They refer to the currently visible frame, in relation to the start/end time of the line you have selected. So this frame is 898ms after the start of your sign and 4102ms before the end of it. If this is where the fade in ends, you need about 900ms fade in. No need to be too precise, one frame is about 40ms, so 10ms more or less won't make a difference. You will then have \fad(900,0). If there's lead out too, you do the same but use the second number. 2015 Note: Apply fade makes it much easier. Another example of a title. This one uses \blur10 (for the border). Be aware that using this much blur may cause lag, so only use it on simple static signs. 2015 Note: That was 2011. \blur10 shouldn't be a problem anymore. Notice the positioning, too. The top of the typesets is aligned with the top of the japanese sign. Don't just throw the signs somewhere randomly, try to align them with something. Also for episode titles and such, try to keep the same positioning in following episodes.

A few notes on using blur. It works differently depending on the borders and shadows you're using.

This is text with no border, no shadow, no blur... \bord0\shad0\blur0:

No border, no shadow, with blur... \bord0\shad0\blur2: Border, no shadow, blur... \bord4\shad0\blur2:

You see it will only blur the outline, not the body.

Border, shadow, no blur... \bord4\shad4\blur0:

Border, shadow, blur... \bord4\shad4\blur2:

This blurs both the border and shadow, but not the body.

No border, shadow, blur... \bord0\shad4\blur2:

This, however, will blur both the shadow AND the body. So you see it's the border that determines whether the body will be blurred or not.

You can't separate the border from shadow for blurring. If you use both, both will be blurred. To bypass that, you'd have to use 2 layers. More on that later.

Same if you want to blur the outline AND the body, you need layers. It would look like this: "Test" is regular mode, "Blur" is 2 layers with the body blurred. Again, more on that in the "Layers" section.

Note: This works the same with 'blur edges' - \be. Experiment to find out the difference between one and the other (shows more with higher values).

Two things related to blur:

Blur is the most essential tag for typesetting. Signs without blur look like shit, so never forget to use it. What I do before I start is to use a script to add blur to all signs. Check the scripts section. That way you start with blur already present on all signs. 0.5-0.6 will work most of the time. You'll change it to higher when needed.

NOTE 1: Do not use \blur.5 instead of \blur0.5 NOTE 2: \blur0.3 does nothing visible. \blur0.4 blurs VERY LITTLE and is only applicable for really sharp video. Don't swarm the script with \blur0.3 when you can see it's not doing anything. ALWAYS check signs at 100% zoom. Use your damn eyes.

Here you can see 3 modes of using blur:

1. The "Mer" part. Completely wrong, because it's only 1 layer and the body is not blurred. 2. The "maid" part. Also wrong. It's two layers, but the primary color of the bottom layer is the same as the top layer. 3. The "Meal" part. This is correct. You can see it looks like the japanese sign.

If you can't see the difference, you're blind (or your monitor is terrible) and probably shouldn't typeset.

When I separate the layers, it looks like this:

This is one of the most important things to learn about typesetting, so make sure you get this right. The middle part doesn't work because the blurred edges of the top layer create partial transparency, so you can partly see the sharp edges of the font body of the bottom layer. Same thing happens when you use \1a&HFF& on the bottom layer, so DON'T do that either. The "Layers" section of this guide explains it in detail. Sort the script by time! [menu -> Subtitle -> Sort All Lines -> Start Time] If you don't do this, vsfilter will screw up blur pretty much whenever there are two or more lines visible on the screen at the same time.

Top is sorted by time:

Bottom is not sorted by time. You can see the border on the default style is screwed up. This and worse things happen when you don't sort the sript by time. Of course you don't need that when working, and it's more convenient to have different sorting while working, but always sort the final script you're putting in a release. 2015 Note: This is not an issue anymore.

Back to the basics... Here's another title. Very simple but beginners will often fail. This one has a shadow but does not have a border. Beginners will often use a border because there's something black around there and who would bother to look carefully... border is first so... bam! Well, nope. If your style has border, use \bord0 to kill it. Align your typeset properly. It would be dumb if it was clearly closer to one side. Don't put it under the sign here because it might overlap with main dialogue. Other things to pay attention to: the shadow in this case is not transparent at all; get the shadow distance close enough to the original; try to match the thickness of the letters; and for god's sake don't use a sans serif font like Arial for this.

Alternatives that work: These two are fine. The thickness matches, they have some pointy ends like the original, horizontal lines a bit thinner than the vertical ones... everything all right.

These two are not too bad, but not as good as the previous examples. They're a bit too roundish, lacking any pointy/thin parts.

Alternatives that don't work:

Sans Serif doesn't fit here. Square ends don't match at all. Looks dull and inelegant. This is too thick/wide.

While handwriting is often useful for typesetting anime, because of the calligraphic nature of kanji, here the kanji is actually pretty simple and orderly. The handwriting looks too disorganized. Next episode title. Pretty simple - get the sizes right, choose a reasonable alignment, get the border color right and use blur. 2015 Note: The inner part is actually lacking blur. (I sucked in 2012.) See section on Layers. You can see it's pretty easy to match the original, so I don't want to see things like this: Using thin outline without blur = nope. Using thick sans serif font = nope. Vertically it's not aligned with anything. That's a fail on a sign that takes a minute to do right. Here's something more interesting. In case it wasn't clear, the smaller circles with To Ra Do Ra are typeset. So what you need is letters and circles. The easiest way to make circles is to use a font with symbols, like wingdings. Find out which letter is a circle and use that. 2015 Note: Please no. Use vector drawings. Masquerade makes circles and other shapes really easy to use. Then find a font that has round edges and isn't too thick. Mine was actually too thin but i solved it by adding some outline in the same color as the primary - white. It was also narrow so i used something like \fscx120. All of this can be set in the style so no tags needed. To get the letters exactly in the middle of the circles, i used \an5 - align to center. That way you can use the same \pos coordinates for both the circle and letters and you know it's right in the center. Now you just need to find the right place to put the circles. Make sure the vertical coordinate is the same for all of them, and that the spaces between them are always the same. The only thing left is to get the right color for each circle. Tools for colors are above the typing area, use the eyedropper tool to get the exact ones you need. Speaking of which - always match the colors exactly, not just approximately. Simple typesets for some names. Handwriting font, match the color, no border, no shadow, use blur. Easy.

This close up is different than what they had in the first screenshot. It's thicker and darker so you can use bold, or outline in the same color... however...

If you're gonna use some outline to make the font look thicker, make sure you can actually afford it without making the font look unreadable.

This is already pushing it, though still not too bad:

Without the border for comparison: This is pretty bad:

Letters like 'e' or 's' become hard to read, especially if you stretch the font in one direction. Please avoid stretching fonts more than about 10% in one direction unless you have an extremely good reason. In this case the letters even merge with one another, so try to find a better font instead. White font, thick dark red border, no reason to fail on this. Clearly here you need some handwriting/cartoonish font, and not some Arial/Times New Roman thing. [Actually this fails with blur, but hey, it was a long time ago.] Sometimes you have a bit more to typeset than one line. Here you need a simple sans serif font. I used this one not because it was the best but because I was already using it in the episode and it was good enough. 2015 Note: It should be at least bold/thicker, and the colour is wrong. It should also have a "glow", but back then that would have lagged. Aside from the "49 New Messages" in white, this is all done in one line.

Dialogue: 0,0:08:08.63,0:08:08.67,mail,Caption,0000,0000,0000,,{\blur0.8\c&HBD8B5F&\pos(126,186)}Subject Thanks!\N\N\N\NSubject This is Chihaya\N\N\N\NSubject It's getting warmer\N\N\N\NSubject It's starting to rain\N\N\N\NSubject Rain was leaking into our clubroom \N\N\N\NSubject This is our clubroom. \N\N\N\NSubject I got in trouble with Dr. Harada \N\N\N\NSubject How do I cut down on faults? \N\N\N\NSubject This is Chihaya \N\N\N\N Subject Karuta players are... \N\N\N\NSubject About hakama \N\N\N\NSubject Guess what happened today \N\N\N\NSubject Notice for the \N\N\N\NTokyo regional tournament

You can see there are 4 line breaks between the text lines (\N\N\N\N) so that I don't have to make 6+ separate script lines to typeset. Choose font size that will make the lines fit in between the japanese lines. When you have the font size, make spaces between the Subject and the rest of each line. You could typeset each line separately but... the whole thing was scrolling up in a non-linear fashion. That also means you can't use \move. So I did this frame by frame, always changing just the \pos tag (you may notice the whole line has more text than you see on the screen - this text scrolls up in the following frames). It was about 20 frames so I had 20 lines in the script. If you typeset each line of text separately, you'd have more than 10 times as many lines in the script. A 20-frame sign is usually pretty pointless to typeset, but the way I did this wasn't really difficult and didn't take much time so I did it anyway.

[Note: The color should be darker and the font should be thicker.] This was not bad a few years ago but is pretty bad now. If you can't match the colors precisely, you suck. The Japanese is not black and white, so use the eyedropper tool to get it right. You can easily make this so natural that it won't even look like it was typeset. You could also use \fscx110 or so for a better match. And it's missing blur. So I gave somebody the task of typesetting this... and this was his first attempt. Positioning is ok. 1 point there. Colors are fine as well. Another point. Alignment of the text is... well, pretty default. More on that in the next chapter. I don't know why the red sign is serif and the rest is sans serif when the jp signs are all the same font. Also the red looks like crap on the light grey background. All that would be passable for a beginner if it wasn't for one obvious problem - no blur. Just adding blur would make it look much better even with the other problems. Here for reference is my own typesetting. You can see the blur makes it blend in beautifully, though the slant helps a lot as well, and the font is much better than the Arial-ish thing above. 2015 Note: It needs "glow". As a sidenote, see the hand moving "over" the Guard Ships sign? That can be done with the \clip tag. More on that later.

A simple typeset: 1. Matching font with roughly matching thickness of letters. [as the English is usually longer, you may need a lot more letters than the jp, so you can't always match the thickness.] 2. Matching colors. 3. Matching border size. 4. Two layers for blur.

That should cover the basics. Just a few more notes. Sometimes instead of blur you can use \be - blur edges. With value 1 they're pretty much the same but with higher values you'll see the difference. Other tags you can use to override the style are \fscx, \fscy, \fsp... again, you should know all these from the link mentioned at the top. I didn't explain \pos because it's so basic that if you can't figure it out on your own, you're hopeless. \an can be useful for signs with a line break - \N. Type something short, then \N, then something long, like "This is \N a meaningless test sentence." Use \pos to place it somewhere on the screen. Then add \an9 or \an1 to see how the text changes alignment while using \pos.

One last note about changing margins. Let's use this screenshot:

Numbers 5, 6 and 7 are left/right/vertical margin. Change those numbers to change the margin. The values don't add up, they override the defaults. It's only meaningful when you're NOT using the \pos tag, mostly for default dialogue. You can use this if you need to move the subs to avoid overlapping with something else. For example changing right margin to 500 will move them to the left, changing vertical to 100 will move them up etc.

« Back to Typesetting Main Typesetting: Aligning Signs [this silly text is here only to align the title with the pictures below...]

Aligning signs is actually pretty easy, but for some reason many typesetters fail in this area all the time. I'm not sure if they don't know how to do this or they're just too lazy to do it right, but it's pretty lame because just aligning the sign correctly will make it look a lot better.

The tags you use for this are the three rotations - \frz, \frx, \fry, shearing - \fax, \fay, and to get the \frx and \fry right, the origin point of the rotations - \org.

Let's try a basic example. Let's say you want to put a sign on this balcony:

This is pretty typical for anime. You will need to make lots of signs of this type, whether it's the notorious nurse's room at school, a sign above or on the door of an office, or various signs on buildings etc. These signs usually have a horizontal slant, but vertically they're pretty much straight.

This is important to acknowledge if you don't want the sign to look like shit... This could be the first thing to do - use \frz to rotate the sign. Unfortunately that's where some typesetters end. If you can call them typesetters. It's the same people who will also pick an incredibly shitty font, often some M$ or Adobe nonsense that looks like crap but is 10 MB large. Anyway, we'll get to fonts later... So if you actually want to do some real typesetting, here's the next step...

The tag you use here is \fax. When used with a negative value, like \fax-0.1, the effect is like using italics. With positive values it leans in the other direction. You have to use low values, usually in the range of 0.05-0.5. It seems that many typesetters don't even know this tag, or are too lazy to use it because unlike the rotations this one doesn't have a tool in aegisub and has to be typed out. It is, however, more useful than \frx\fry, because anime doesn't have much of 3D effects. Most of the time it's really just horizontal slant while vertically it stays the same.

So now you have a sign that's fairly well aligned. Now you just need it to blend in a bit better...

...which you achieve by changing the color to match whatever is relevant in the picture. Here I don't have an original sign to imitate, so I just go with the outline of the balcony. You will also notice that i used blur. On signs, using at least 0.5 blur is a must if you don't want them to look obviously added and out of place.

So now you have a pretty decent sign. You might as well go with it. If you look more carefully though, you'll see that it's aligned to the bottom of the balcony but not the top. Why is that? Well, because here you actually have a sort of 3D effect, where the left end of the balcony is taller than the right. So what now? Since we've gone this far, we're not gonna redo it with rotations, so we'll use a simple trick... Looks better, doesn't it? (By the way if you don't see the difference here, you should probably not be a typesetter.) So what sorcery is this? Very simple. The original font size was 50. If you want the end of the sign to look smaller than the beginning, instead of all kinds of rotating and skewing you can do this... Sa{\fs49}mp{\fs48}le {\fs47}Te{\fs46}xt As you can see, after each 2 letters i decreased the font size by 1.

If you're learning to typeset, this should be good enough. If you're pro, you'll notice the end of the sign is still a bit too tall, the 'p' is too close to the bottom etc. And obviously you'll want something better than Arial. Then again if you were a pro, you wouldn't be reading this guide.

So we have what we wanted, but let's look at other ways of doing this. This is without using \frz and \fax, but using \fay instead. Often this is more convenient because you don't have to use any rotation. It'll work fine for signs on/above doors much of the time. The problem with \fay is that you can't use additional tags in the text like I did for the font size before, because of a vsfilter bug.

The last way to do this that I'm gonna mention, possibly the most pro if you can do it right [but epic fail if you can't], is using just the rotations and moving the origin point. This means we're gonna use \frx and \fry, instead of \fax and \fay and do it right. Why would I do that when I've described how \fax\fay is a lot more convenient? Because the rotations actually can give you a 3D effect when you need it, and we've noticed that the balcony here is "closer" on the left. We've managed to bypass it pretty well with the font size, but that may not always work. Like when the sign is only 2-3 BIG letters.

First let's look at what will happen if you use rotations without moving the origin point.

You'll see many "typesetters" create these abominations. It's possibly even worse than our first example in black color way above. What you have here is a sign where maybe 2 out of 10 letters are aligned somewhat correctly. The rest is FUBAR. This is like "I has Aegisub so I can into typesetting yeah?" Nope. You can't.

So what now?

If you try playing with those rotations, you may find out that no matter how much you rotate it around, it just doesn't fit. It will always be aligned at one end and not the other. Why? Because if your default alignment is \an2 or \an8, it will always align to a vertical line that goes through the center of the sign. That means both sides of the text will lean towards the center, like you see above. Now if you use \an1, 3, 4, 6, 7 or 9, you may get slightly better results, but still pretty derp.

So what you need is to move the origin point. The tag is \org(x,y), but you can use the rotation tool. You'll use the tool for \frx\fry and rotate slightly to the side - like \fry7. Usually you want to use only a little of these rotations and get the rest done with \org. If you use too much, you'll get too much difference between one side and the other.

When you adjust \fry, you grab the triangle in the center of the grid and move it. In the tags you'll see \org appear and you'll notice the sign changes alignment as you move it. Then you just have to go and find where to drag it to make it align right. It may often be off the screen. Once you go off the screen and let go, you won't be able to grab the triangle again but you can adjust the numbers in the tag. But since that's inconvenient, try to learn to get it right the first time by dragging.

Now, you'll notice that while this will let you get the alignment right, it may drag the whole sign away from its position. This is not a problem, you'll get it back, just get it to align right first. When it seems fairly good, you switch to the Drag tool. You will now see 2 points you can drag - the regular square and the triangle. You won't see the triangle if it's off the screen, but the red line will tell you roughly where it is. So now you drag the the sign back to position with the square. If it misaligns the sign, you need to drag the triangle a bit again. It may take a while to balance them out, especially if the triangle is off screen & you have to change the numbers in the tag or try again. With a bit of practice though, you'll learn to do it more easily. More importantly, you'll get some good results...

You can see that the sign looks pretty good and the only tags creating the alignment are \fry and \org. (\frx0 can be deleted, it's just that the tool adds both tags even if they're 0) The script resolution is 1280x720 so you see the origin point is off the screen in this case. This method is especially useful when you need to do something more complicated like you'll see in the last example at the bottom of the page. Of course you can still fine tune this using \fax and/or \frz.

You may use various approaches and combine the tags as you wish.

For static signs \fry\org may be the best, but if you need the sign to move, you can't use \org, so go with \frz\fax and possibly scale down the font size for some perspective if needed.

So, that's about it. Now you know how to align signs and do it right.

A few examples of signs I've found in some groups' releases... On the left is the original sign, on the right is my quick adjustment. Clearly this looks bad and totally out of place - alignment is terrible, even the \frz isn't quite right. You can also see the color is off. I didn't like the choice of font either but that's the least of its problems. Even my sign doesn't look too great, but at least it doesn't look retarded.

While in the previous example it was only \frz and good-bye, here some "pro" went for \frx and \fry, clearly without a clue how to use it. It is sad, because it only takes a minute to do what I did on the right. Whoever did the one on the left should be fired. I wanted to just quickly fix this to make an example so I went for \fay. If I actually wanted to release this, I'd use \frz and \fax, and then change color after every 2-3 letters to match the change of color on the japanese sign. It just keeps happening, doesn't it. I'm not sure why groups that have been around for years have problems with this.

Now this... is admittedly not an easy one, but this implementation is pretty poor, especially with each line having different (mis)alignment. The "When would be good?" line is so bad that I'm guessing the author must have been in quite a hurry to finish this. Here's where \fax and \fay won't be enough and you'll have to use rotations, and you'll have to use the \org tag, if you really want it to look good. One other thing you'll have to use is blur. Its lack in the one above makes it even worse. And the colors are off as well. It will certainly take more than a minute, possibly even 10-15 minutes to get all the 4 lines right. But hell, if you look at the one above, and the one below that I made, you'll have to admit that it's worth the time.

« Back to Typesetting Main Typesetting: Positioning Signs

There are several things that determine how good a typeset is and how well it blends in. Obviously, the colors and sizes of font, border, shadow. Then of course the choice of font. The next thing would be the positioning of the sign.

Beginners will have this idea that the typeset for a sign should be as close as possible to the original sign. Please get this idea out of your head, because half of the time that won't work very well. It is especially unnecessary for typesetting titles. If it's a sign that belongs to a specific place on the screen, then sure, you wanna get your TS close to that. But an episode title has little relation to what's on screen in terms of placement, so no need to go crazy trying to match the position and orientation of the sign while sacrificing readability.

I used an episode of Nichijou for a test, here are the results from some guys.

Kanji is often written top-to-bottom, rather than left-to-right. It works great with kanji. It does not with English. So don't try rotating the text like this just to match the orientation of the japanese sign. It decreases readability and doesn't really look that great. You might think of using M\No\Nt\Ni\Nv\Na\Nt\Ni\No\Nn. In other words top-to-bottom, without the rotation. Well... try it. For one, you will usually have large spaces between the letters. But even if you pick a font that doesn't do that, or do each letter separately, you'll run into another problem. Kanji has pretty consistent width. English letters don't. The difference in width between M and i can be anywhere from 500-1000%, depending on the font. So this will usually not work either.

Also here ^ the white 'shadow' is missing. This is a pretty good placement. It's much better to sacrifice font size and orientation if the result is well readable and looks good. The shadow is a bit too thin and jagged but this is overall fine, except that the font should be a serif one.

You can do something like this. Pick a font that will look good, you can make it large enough so that it's both readable and close to the size of the kanji, and keep at least some kind of alignment when posotioning it, in this case the top aligned with top of the the kanji. You can even put it on the other side of the screen for overall symmetry. If you want it top-to-bottom, it may be better this way than rotating, but I'd say it's almost always better to keep it left-to-right. Trying to split the text like this will rarely work well for a number of reasons, like the width inconsistency that you see here.

Another title. Here I think if the font was larger, to match the span on the kanji precisely, thicker, and placed a bit farther, it might actually not look too bad (putting aside the readability issue)...... but clearly this works much better. It's aligned to the centre of the kanji, looks fine except for the font.

Here aligned to top, with a different font.

There's never one "correct" place for typesets. Rather than following some rigid logic for where a sign should be, just make it "look good." It doesn't even necessarily have to be aligned with the original sign in any way. Here it pretty much ignores the jp sign. Instead it's in the middle of the "background" area - the sky - where it doesn't interfere with the foreground. It's kind of where you'd put it in the first place, if the jp one wasn't there at all... pretty much the most natural place for a title on this screen, assuming the title is horizontal.

If the jp is in the middle, and you can't exactly fit the typeset in the middle as well, you may split it like this (if the words allow it). This should be pretty obvious. You don't want the sign over Yukko's head, and you don't want it over the papers above either. The neutral green area is the most suitable place. If you split the title in 2 lines, you could put it in the large green area on the left.

Here you have 2 typesets and main dialogue, so first of all you want to avoid any of them overlapping. The title is where it is pretty much out of necessity. There's hardly any other place suitable for it. The office sign... many would try to fit it on the white board, along with the japanese. That can certainly be done, but you'll have to have really small font size and it'll still look cramped. So I put it above, and matched the width of the sign and thickness of the letters roughly. It looked too artificial without that shadow, so that was added to give it some sense of space, even though as a "shadow" it's illogical.

Here again trying to squeeze the Shino Labs on the signboard would be frustratingly difficult & just wouldn't look good no matter what.

On an unrelated note, did you know sharks can fly?

Anyway...

One other option is to simply replace the jp sign with the English one. That will, however, only work well if the background is one solid color. Here's one where it will be simple enough: You'll be creating 2 layers. One will just draw a blue rectangle over the kanji. The other will put the English text over it. Normally I make the actual sign first. Then I duplicate it and delete content. Then paste this instead: {\p1}m 0 0 l 100 0 100 100 0 100{\p0} That's a basic square in drawing mode. Nuke border/shadow if present. Expand it using the scaling tool (\fscx\fscy) to the size needed to cover the kanji. Add \blur1 to make the edges softer to prevent them from being noticeable. Match the blue color and adjust layers so that text is on top of the rectangle. The result will look like this:

And just in case this was difficult to comprehend, here it's disassembled:

You could create more complex shapes than a rectangle. More on drawing and other stuff in this section.

...possibly more examples coming later...

« Back to Typesetting Main Typesetting: Moving & Animated Signs

There are several kinds of moving signs. Ones that are moving in a constant linear fashion, ones that are accelerating/decelerating, and ones that do various other things - rotate, shake, etc.

Linear movement is simple. You use \move(x1,y1, x2, y2). Let's say you want that fish on the bottom right follow the kanji moving to the left.

First you use the Drag tool and position the fish in the first frame. Then you click the blue arrow that the other blue arrow is pointing at. That switches from \pos to \move. Then you click on the right green arrow the other green arrow is pointing at. That gets you to the last frame of this fish. That is assuming you've timed your fish correctly. If you haven't, then you're dumb, because what are you gonna do with a timeless fish? Anyway... When on the last frame, you grab the circle that appeared over the square (on the fish's belly) and drag the fish to where it's supposed to have swum. Like there^. You'll get a tag like this: {\move(1195,650,1009,652,0,799)}<°)))>< Now when you see the vertical coordinates are 650 and 652, you did it wrong. The fish is supposed to be swimming just horizontally, not up and down, so the coordinates have to be the same. So you'll correct whichever one is wrong.

The last 2 numbers are the start frame and end frame timecodes. They are useful if the movement occurs only over a part of the sign's duration. For example if the fish changes its mind in the middle and stops swimming, the timecodes will be "0,400". If you're using the whole duration, then I suggest you remove those last 2 numbers once you've done the positioning, since they tend to make the fish 'slow down' at the last frame. It may throw the positioning off a bit, so you'll correct it by typing, because if you use the tool again, it will add those numbers again, and you'd be chasing your tail like... a fish? (or whatever animal does that, I dunno) [As far as I can tell those last 2 numbers don't seem to cause any such issues in Aegisub 3.0. I got used to leaving them in and everything works fine.] 2015 Note: Actually things will be wrong if you nuke them. (It's a bit complicated, but the sign on the first frame isn't really at "0".) If the movement doesn't cover the whole duration of the sign, then whatever frame you click the square on sets the start time, and whatever frame you click the circle on sets the end time. Again you can adjust that by typing if needed. 2015 Note: In later versions, clicking isn't enough. You have to actually move it.

Speaking of Aegisub 3.0, here's a trick for making the movement precise [doesn't work in 2.1.9]: First place the sign next to something that makes it easy to define the position precisely. For example make the fish's nose touch a corner of a letter. Or you could overlay the fish's eye with the circle/period at the end of the moving text. Then go to the last frame and do the same there. The point is to choose a reference point where you can easily tell it's positioned precisely the same on the last frame as on the first. Then go back to first frame, switch to standard mode [above the drag tool on the left], and double click somewhere on the screen. This will place the starting point of the fish there, while keeping the movement the same [keeping the distance and direction]. Then just click a few times until you get it positioned exactly where you need it.

Make sure to check the video once you're finished so that you don't end up like dickpants with the sign starting god knows where and moving in the opposite direction than it was supposed to... If you haven't done anything incredibly dumb like this, you'll still check if the fish is swimming at the right speed. If it seems like it's getting closer to the kanji, then you change the ending X coordinate to higher value [ie. more to the right]. If fast then other way round.

OK, so... that was the easy part. The trouble comes when the movement is not constant and linear. If that happens, you have several options.

1. Quit fansubbing. 2. \an8 3. Ignore the inconsistencies and use linear movement even if it doesn't match. 4. Use \move but split it into several phases to decrease the inconsistencies to minimum. 5. Do it right, ie. frame by frame, either using a tracking software or by hand. ad 1. Good Bye ad 2. Nope. See point 1. ad 3. This may be ok when the actual movement is not too far from a linear one and there won't be significant inconsistencies. For example if the movement is generally linear but a bit twitchy. Otherwise this option would be pretty dumb. ad 4. This is kind of the middle way. The more phases you split it into, the better it'll look, so it's just up to you and how much time you wanna spend on it. This will mostly be useful for movement that is pretty much linear in direction, but accelerates/decelerates. What you do is duplicate the line a few times, and time all the lines to make a sequence. So if it's 5 seconds, you can split into 5 lines, and time them (in seconds) 00-01, 01-02, etc. In reality you will need like 2-3 segments per second to make it look somewhat decent. Then you just use linear movement for each line, the next line always starting where the previous ended (or a little bit farther).

You'll get something like this:

Dialogue: 0,0:17:15.94,0:17:16.44,Default,,0000,0000,0000,,{\move(238,315,373,315)}text Dialogue: 0,0:17:16.44,0:17:17.05,Default,,0000,0000,0000,,{\move(377,315,465,315)}text Dialogue: 0,0:17:17.05,0:17:17.61,Default,,0000,0000,0000,,{\move(467,315,508,315)}text Dialogue: 0,0:17:17.61,0:17:18.16,Default,,0000,0000,0000,,{\move(511,315,542,315)}text Dialogue: 0,0:17:18.16,0:17:18.68,Default,,0000,0000,0000,,{\move(544,315,552,315)}text

[Note: It's 2013. We don't do this shit anymore. Use mocha.] ad 5a - using tracking software. We use this thing called Mocha, and we now have a guide for using it here. At this point this is pretty much a must. ad 5b - by hand. This should rarely be needed, but there are times when mocha fails. Instead of splitting into just several segments and using \move, you'll split it into as many segments as there are frames for the sign, and you'll be changing the \pos coordinates for each frame. As the japanese save on animation, often 2-3 consecutive frames are the same, so sometimes you can have 2-3 frames per line instead of 1. If it's 2-3, then you'll need regular frame timing, if it's each frame, you can time the 1st line to 1 frame, right click on the line and select "Duplicate and shift by 1 frame" [or Ctrl+D]. This way you'll be getting consecutive frames, each timed to 1 frame, which is exactly what you need. [If each 2-3 frames are the same, you can still do ctrl+D and then "Join (keep first)" the lines that are the same.] After that you go through all the frames and adjust position with the Drag tool. The interesting thing is that while this method produces the best results, it's not even difficult. Pretty much all you need to be able to do is time the lines and set \pos, which is really the very basics. The problem is that it's quite time consuming and somewhat tedious.

One more thing to cover is signs that rotate, expand etc. You could still do this frame by frame, but usually there's a better option. You can use the \t tag, which allows you to apply gradual change for specific tags. How it works is described here. If you need the sign to spin 360 degrees, you'll use \t(\frz360). If you need it to spin twice, it's \t(\frz720). To spin and stop after 1 second it's \t(0,1000,\frz360). To do the same but start spinning slowly and accelerate it's \t(0,1000,3,\frz360). So you have \t(start time, end time, acceleration,\tag1\tag2\tag3...) as explained in the link. If you need it to rotate around a different point, you'll use \org for that (described in Aligning Signs). You can combine this with \move, so it can be moving and spinning.

Other tags you can use with \t are \fscx \fscy \fsp \fs \blur and a few others, including colors. It doesn't work with everything, like \pos \org etc. Experiment with this to find out what works well and what doesn't. You can use several tags at once. You can achieve all kinds of things with the \t tag. The main downside is that if you use too much of it, playback will lag. And it can lag A LOT.

A few more related things wil be explained further.

« Back to Typesetting Main Typesetting: Using Layers

Probably one of the most useful tools, once you make it far enough that you're actually trying to make signs look nice, is layers. You can use them for various effects. The two most important ones are:

1. You can have multiple borders/shadows 2. You can have blur between primary and outline color, which makes signs look a LOT better

If you're inventive, you can always add some extra effects to that.

^ This sign here has 2 borders - orange and dark grey. Clearly you can't make it in one line. So what you do is you make one sign, with orange border and no shadow, then duplicate it (right click on the line in script), and change one of them to larger border in dark grey and add shadow. For example you'll have \bord2 for the orange one and \bord4\shad2 for the dark grey. The important part is that the orange one has to be on top. For that purpose there's this thing called layers. You can change the layer number above the typing area, next to the start time:

So the orange one will be layer 1 and the other layer 0, or whatever other numbers but the orange one has to be higher. You will also use this when regular dialogue subs overlap with signs! The dialogue has to be on top, obviously.

Some notes about this sign: The jp doesn't have an orange border. The primary color ranges from yellow to orange. Choosing only one would make it look too plain and you can't use a texture instead. (Well, you could probably imitate it somehow if you were inventive but this is about basics.) I wanted to have both yellow and orange in there and this was the easiest way to do that. Also the font was pretty thin and this made it thicker.

If you're looking carefully and actually paying attention, you have noticed the white "dots" on the sign. The jp sign has light reflecting off of it (coming from top left), creating blurry white areas. Since I like doing things that nobody else does, I wanted to try to imitate this somehow. It's a kind of silly idea that nobody would bother even thinking of, much less trying to do it, but for me, work without creativity would be too boring. Obviously this is nothing like real light effects, but I think considering the tools available it's not all that bad. So how did I do this? Create a third line, layer 3, white color, no border/shadow. Type some commas with a few spaces between them. Use a lot of blur. Then just figure out the sizes and spaces and rotation etc. to make the dots fit into places you want them at (keeping the light from top left pattern). That's it.

This sign still has some flaws and could be better, but let's just say that if you have a script with 30 signs of varying difficulty and you need to sleep soon, you don't always do your best.

I use double layer signs quite often mostly for one specific reason. If you check the sign above, you'll see that despite the blur on the outside, the border between yellow and orange is sharp. That's because when you use blur, it only blurs whatever's on the outside, ie. if you have an Outline, it won't blur the inner letters. That is sometimes a problem because the japanese signs are usually blurred on the inside as well, and the sharp edge between the inside and the border just looks bad.

The solution to that is to create another layer without the border and blur it as well. In other words you duplicate the sign, make one layer 1 and add \bord0.

I recently redownloaded the video and fixed the colors and blur [keeping the font though]:

Here's another example: The first 2 letters "Re" are only one layer, without the inner blur. The rest is 2 layers as described above. The red in the "Re" is sharp and compared to the kanji looks bad. For illustration I also added one more layer to the right half of the sign, adding the soft, blurry, reddish background. It's the lowest layer and uses reddish border with lots of blur. I didn't use this in the release, this is just to show that it can be done. It's actually \blur12 and the sign is fading in and would have 3 layers, so it might lag.

This sign could look much better - I think I did this on a workraw, meaning small, blocky video with poor colors, so I couldn't see details very well. Anyway, 12 episodes got released at this point and no one's complained so far, so I guess it's ok. 2015 Note: I think the inner blur is still wrong because \1a for the white layer is probably red. (Explained further below.) This was quite a challenge (in 2011), for several reasons. Blue letters, white edge on the left, black on the right, and blurry shadow behind it... what to do? On top ot if, the letters were appearing one by one... but before we get to that, we have to deal with the basic design first. Obvious things like font, size, position and main color should be... obvious. If we want the white/black edges and the shadow, we'll need at least 3 layers. You can probably figure out how to do the black edge on the right and the blurry shadow. The black edge is regular shadow, and the background is another layer with more blur. But what about the white? Well, aside from needing another layer for that, there are some extra tags that will help us. Besides \bord there's also \xbord and \ybord, and besides \shad there's \xshad and \yshad. This allows you to extend the border differently vertically than horizontally, and make the shadow be cast in any direction. Here I needed shadow (white) so i used \xshad-3\yshad-1, which positions the shadow 1 pixel above and 3 pixels to the left (yes, negative values to go up and left). The whole thing looked like this: layer 2: {\fad(0,600)\fs150\fax0.1\bord2\xshad-3\yshad-1\4a&00\blur0.8\pos(620,280)\3c&H9E362E&\4c&HE7C4B4&\c&H9A2B24&}R-Really... layer 1: {\fad(0,600)\fs150\fax0.1\bord2\shad3\4a&00\blur1\pos(620,280)\c&H9B352E&\3c&H9E362E&\4c&H250F09&}R-Really... layer 0: {\fad(0,600)\fs150\fax0.1\bord0\shad0\blur4\pos(628,288)\c&H230E0C&}R-Really...

[\4a&00 changes the shadow to opaque, because the default was partly transparent - it's a variation of the \alpha tag.]

Well, this was only the last part. As I said, it was appearing letter by letter. First I used \t and \clip to make it appear continuously. Good thing was I still had only 3 lines in the script but it turned out it was lagging like hell. [Note: having 3 layers with \t on all of them is quite likely to cause lag.] So instead I had to go for alpha timing, making it appear letter by letter (almost, because the jp has fewer letters). This was still pretty intense but worked out without lag. It was a bit more work and I ended up with 18 lines for this sign, but it looked pretty cool. Signs with border and blur - how to do them right

After typesetting a dozen shows I realized that making 2 layers for all signs with a border is a pretty essential thing if you want the signs to look good. It's pretty much one of the most important things that will distinguish a good typeset from a mediocre one, without much effort. It's fairly simple and easy to do but it took me some time to figure out how to do it right.

The first step was to just make 2 layers and nuke border on the top one [of course blur would be there from the beginning, so by duplicating the line it's on both]. Then I realized that there's a problem with that. Let's say you have black primary color and white outline. You make 2 layers, nuke the border on the top one, and have let's say \blur0.6. The result, however, will not be real blur0.6 on the black color. The blurred outline has by definition some transparency, which means that the sharp black border from the bottom layer is still partly noticeable. How much will depend on the colors. Sometimes it looks pretty good, other times it's almost as sharp as it was in one line.

Here we have two examples. For both of them - left is one line, middle is two lines as described above, right is the correct way [we'll get to that in a minute]. You can see the left ones are too sharp and the right ones look good. The middle one for Touch is not bad, but the letters got a bit thicker. On the Click the middle looks just as bad as the left one [just thicker, too]. So you see this is not working right. One idea that I had, and apparently others too, was to make the primary color for the bottom layer transparent. While that sounds reasonable, it doesn't really work either, because the inner edge of the outline is still sharp. So again it will depend on colors used how that's gonna look. Here's an example:

You can see on the lighter colors it looks ok [because there's a light color behind them], while on the darker ones there's a thin white line between the primary and outline. The blur on the top layer makes partial transparency just before the outline, and the bottom layer is fully transparent for the primary color, so you can see a bit of whatever the background color is. If the bg happens to be similar to your primary color, it may look ok. But it's not something you can rely on.

So the final trick that makes it look good is to make the primary color of the bottom layer the same as the outline. You can see the result on the right part of the Touch and Click pictures, or on these two signs here: While it may seem like a bother to make 2 layers for almost every sign, it's not really that much effort. Especially since most of you reading this probably don't have more than 20 signs per episode most of the time. I've worked on Maria Holic Alive and Acchi Kocchi [in case you can't tell], which means about 80 signs per episode and I make 2 layers for anything that has border.

Here's the routine to do it [except it's 2013 now and we use scripts for pretty much everything we do]: 1. type the needed blur and border to your line 2. use eyedropper to select primary and outline color ...that's what you'd do anyway, now the layers... 3. duplicate the line [I use just ctrl+C/ctrl+V since it's probably the fastest] 4. change the top line to layer 1 and rewrite border to 0; hit Enter to get to the second line 5. double click on the tag for outline color, ctrl+C, double click on tag for primary color, ctrl+V

In other words, once you have 2 layers, you cange one to layer 1 and set \bord0, and in the other one you copy the color from \3c to \c. It's pretty fast.

But in case you're really lazy, here's lyger's script. I also made my own version with some different options, but it's less tested.

Each of them works differently and one may work better in specific situations than the other, but in most cases either one should be fine.

2015 Note: I don't know if anybody still uses lyger's script for this (I think it's unlikely), but 'Blur and Glow' is well tested and many times upgraded, so it should probably be your primary tool for layers.

Another thing I do with layers is actually for signs that don't have a border [now included in my script as 'Glow']. Sometimes you have signs that have this vague, hazy, blurry surrounding, like in this picture: This is how you'd normally typeset it, and I'm sure you can see the difference. This is a regular blur of about 0.6. If you use more blur, the letters will become unreadable and won't look like the original at all. If you use a border and blur it a lot, it will be all kinds of messed up and look even worse. So we need two layers again. However, I use both layers without border. For the bottom layer, we need something in the range of \blur1 - \blur3. With a border you'd have problems... If you use \blur3 with \bord1 or less, you'll get a sharp edge between primary and outline color. Smoothness gone. If you use \blur3 with \bord2 or more, it becomes unreadable because it makes the letters too thick. The solution is to not use border, and use \blur3 for the bottom layer, and \blur0.6-0.8 or so for the top. This basically gives you a double blur and looks like this: At first glance it may not look like much of a difference, but if you switch between the views [commenting the bottom layer], you can see it clearly. Again, how much better this looks will depend on the colors used, the background etc. Here's another example:

This may be slightly overdone but at least you can see it more clearly. You can't do this in one layer, and you can't do it with border either. The one thing I haven't mentioned yet is that you need to adjust the bottom layer not only with the value of blur, but also color. With the same color as the top layer it will usually look too thick. So you just make the color brighter/darker till you get the effect you need. You could also do this using the alpha tag. With the eyedropper tool changing color may be faster but in some cases alpha might look better. For the color you usually only need to change brightness, but sometimes the outline has a different hue as well.

Here's a more complex example with 3 layers. 2 for blurring the primary and outline, and 1 to add that soft haze around.

When you just look at this, you'll barely notice the 3rd layer. But when it's not there, you'll clearly notice that it's missing and the typeset will stand out. Good typesetting is more about the viewer not noticing somethnig rather than noticing.

Another example: And some more examples of using layers... Typical case of 2 borders. You can make this blend in really well... if you have 3 layers, all with blur.

3 layers... Top layer - shadow, bottom layer - outline. This one... well... let me just show you. Layers top to bottom:

{\blur0.8\yshad-0.5\fax-0.32\frz8.624\move(597,70,597,788)\4a&HCC\4c&HBEB4FF&\c&H000005&}Fire {\4a&H00\yshad-0.5\4c&H1700A4&\c&H010007&\fax-0.25}Ext{\fax- 0.22}in{\4a&H80\fax-0.2}gui{\fax-0.19}sh{\4a&Haa}er

{\blur1\shad6\xshad0\fax-0.32\frz8.624\move(597,70,597,788)\4a&H60\c&H000000&\4c&H000000&}Fire {\c&H06001D&\4c&H000016&\fax-0.25}Ext{\fax-0.22}in{\fax- 0.2}gui{\fax-0.19}sher

{\blur1.5\yshad-0.5\fax-0.32\frz8.624\move(597,70,597,788)\3c&H3511B1&\alpha&HFF\c&H2E0E96&\4c&H240778&}Fire {\alpha&H00\fax-0.25}Ext{\fax-0.22}in{\fax- 0.2}gu{\alpha&HFF}i{\fax-0.19}sher

{\blur1\yshad-0.5\fax-0.32\frz8.624\move(597,70,597,788)\4a&HCC\c&H020108&\4c&HBEB4FF&}Fire {\4a&H00\yshad-2.2\xshad-0.8\4c&HF6F5FF&\fax-0.25}Ext{\fax- 0.22}in{\4a&H80\fax-0.2}gui{\yshad-1.8\fax-0.19}sh{\4a&Haa}er

Kinda hard to explain this, but aside from multiple layers with multiple colors [red, almost black, almost white] you need different effects in different parts. You need more light on the "Extin," "Fire" needs to be much darker, different shadow colors, different shades of red around the letters, different \fax every few letters, varying transparency etc. And the whole thing is moving. Yep, took about half an hour. If an episode only has like 5-10 signs, I play with them a bit more.

One more thing layers are used for - to mask the orignial sign and typeset over it. More on that in the next chapter.

« Back to Typesetting Main Advanced Typesetting

So what remains is clips, animation, drawing mode, and using all the things together.

\clip

The clip tools are under the rotation and scaling tools in Aegisub. In principle they are simple. You have a sign, and you want only part of it to be visible. So you use the basic clip tool and draw a rectangle over the area you want visible. You'll get somethnig like \clip(54,25,380,110) in the tags. That's the coordinates of the visible area. Rather than this part being visible the idea is the other part not being visible, so it doesn't matter if you expand it to empty areas.

It can look something like this:

< with / without the tool selected >

If it's a pixel off, you can either drag the orange dots or type in the tag. Typing without the tool selected may be better as the red lines won't be in the way.

Obviously, a rectangle won't always do so you have the other tool that lets you draw a more complex shape.

There's also \iclip, which does the opposite - selects the area that will not be visible. No special tool for that so just add the i & adjust coordinates by typing. \clip has more compatibility so I use that one wherever possible.

That's the basics of using clips, now for the drawing mode. You should already know how it works from ASS Tags.htm.

Drawing is useful when you need to cover some area with solid color and put a sign over that. I have briefly described that in the Positioning Signs section. I rarely use any complicated shapes with this. Usually only the rectangle. And instead of adjusting the coordinates for size, I've found it's much more convenient to use the basic version {\p1}m 0 0 l 100 0 100 100 0 100{\p0} that I copypaste from somewhere and adjust size with \fscx \fscy. It can also be rotated etc. like regular text so that gives you some variability of shape. If I need a circle, I'll use a font with symbols. If I needed a [static] complex shape, I might use the rectangle together with the vectorial clip. Of course now there are scripts for everything.

Normally I use the drawing mode only for masking. Using the basic rectangle with \fscx\fscy\fax\frz i can usually get what i need.

Here's what you can do with it, if you feel like spending 5 hours on 1 frame:

Some colors of the masks are off because back then I didn't know about the issues with colorspaces and used ffmpeg in Aegisub 2.1.8 or 2.1.9. If you're using those versions, use avisynth to load the video. If you're using Aegisub 3.0, use the BT.601 colorspace [which I think is on by default]. 2015 Note: And if you're reading this, do NOT use BT.601. (Turn it off in Options-Advanced-Video.) Yeah, shit keeps changing.

In case it wasn't clear to someone, all the books had Japanese titles, of course...

So it really was 5 hours of pretty tedious work. I don't recommend that you ever try that. Each book has one typeset for the title with matching color and size, and a mask in the color of the book to hide the jp title. Sometimes there are additional ones for the numbers. Here's what it looks like in working mode. I think it's almost 200 lines. OK, but back to a bit more sane things...

If you need a rectangle with round edges, or even a circle, you can still do it with the basic square. For rounded edges use border with a value as high as you need to get the right shape. \bord20 or \bord30 may be useful values. Of course you need the exactly same color for \c and \3c.

To get a circle you do the same but scale the original square down to 1 pixel.

You can actually use this with a regular font. For example if you use a period with \bord50, you get a pretty good circle. You can use the letter O for an ellipse or whatever else gives you a shape you need. Just match the primary and outline color and it works.

Speaking of that, sometimes you need a mask with a slight gradient. Well, you might need a strong gradient but then you'd have to use an actual gradient and have hundreds of lines... But for a mild one, you can actually use some symbols from a font with large border.

For example in one Nise ep I had this sign:

The background on the left is darker than on the right. Or maybe you could say the left is more orange, right is more yellow. So a mask in one color didn't work, because it was always too visible on one end. And you can only have one color for the drawing mode. So what i did was use 333333333333 with \bord10 as the mask and changed color every few letters. I mean numbers. You can use OOOO or 8888 if you need a roundish mask, or use IIIII or ||||| if the range of colors is larger. Of course for each section the outline color must match the primary. But to make it work you need one more thing.

That brings us to the last part about masks... blurring the mask.

While I start with \blur0.5 on all signs, I put \blur1 on the masks, and more if needed/possible. The reason is that more blur helps it blend better. So if there's enough space around, you can blur it a lot, like \blur5 or more, and then even if the sign has a slight gradient, you may get away with just one color, because with blur5 you always have 5 pixels of fade. [Actually this doesn't really relate to pixels but you get the idea.]

So back to this sign. We have those 333s changing color. For that to work you need that blur so that the different shades can blend into each other smoothly. I used \blur3 here. Couldn't afford more, because it would either start showing the kanji under it, or grow outside of the orange area. If you look hard, you may still find some barely visible discrepancies, but basically here you have a mask with a gradient. For reference the colors are &H3D8FE9 on the left and &H4094EF on the right.

Now for the most fun part...

\t

This is the tag with which you can do almost anything..... and make everyone's player lag. As a demonstration you can try this: Dialogue: 0,0:00:00.00,0:00:06.00,Default,,0000,0000,0000,,{\an5\q2\fs40\b1\bord1\blur0.1\shad0.1\1a&HFA\4aHF0\t(0,3000,3,\fs75\bord4\xbord10\shad22\blur1\1c&H00FFA9&\3c&H9B2664&\4c&H0C1A4C&\4a&90\1a&H00\fscy150\fsp15\frz15\fax- 0.4)}Unlimited Eyecancer Works

This will change font size, border size, shadow distance, blur, all colors, transparency, font scaling, font spacing and rotation. Oh and CPU usage. For even more CPU usage, add movement, \clip, the other rotations and \blur15. 2015 Note: It would be hard to make one line lag nowadays, no matter how many transforms. But if it's many lines at the same time...

So this gives you the idea of how you can change the basic properties. Use your imagination to figure out how far the options go.

Let's try something more practical though. Like text appearing bit by bit. Type some text and place it somewhere. Now use the clip tool to make only the first letter visible. Let's say you get \clip(50,150,100,250). Now add \t, use the same clip in the \t tag but change the second X coordinate so something after the last letter. If the text ends at 400, you'll use \clip(50,150,100,250)\t(\clip(50,150,400,250)). This will be showing static text gradually from the first letter. See how it works out and adjust coordinates as needed if something's off. If you need the text to appear in the first 500ms and stay on screen, use \t(0,500,\clip.....)

Something similar would be text that moves into a visible area, though here you don't actually need \t. Let's say a person is standing in the picture, text is generated behind his\her back, no pun intended, and the text comes out on the right. So what you do is use \move to make the text move from left to right, and you'll use \clip to make sure the text is not visible behind that person. You'll get a sequence like this: You'll have to use the vectorial clip for this, to follow the hairline. You could also expand the clip to the other side of the person and just make the text scroll behind her...... as long as she isn't moving. If she is and you still wanna do this, change \move to \pos & go frame by frame. If after a few hours of that you feel like screaming "Zetsubou shita!" no one will blame you.

Here the text is scrolling bottom to top, hiding behind the bars and the guy. Use a simple \move, tune it so that it moves precisely along with the kanji first. Then use a clip to outline the bar. I set it up so that each of the 2 portions of the text only goes under one bar so that I wouldn't have to clip both for one line. You could, however, lead the clip on the side of the screen to the other bar and cover both of them with one clip. Instead of clipping everything except the bars, you'll just clip the bars and then rewrite the \clip to \iclip, which inverts it. Of course you also have to include the guy's shoulder in the clip.

Here's more fun stuff:

Find an awesome font. Set the colors, border etc. Clip around the guy's head and his tools. Apply \move. This is even more tricky: Create all signs with colors, borders, shadows, layers etc. Use ASSDraw to create parts of the yellow circle to mask parts of your signs. Change color of the letters that are close to the yellow circle. Blur appropriate things for the glow effect. Now mocha track over more than 100 frames as the whole thing is zooming out with random flashes of light. (Then change the episode title and colors every episode.)

Another example where you can combine \move and \t is when text is growing larger. You know the trailer kind of stuff where a line appears and seems to get closer, then a few images and another line. You'll use \t(\fs) to make the sign 'grow,' and \move to make up for any inconsistencies that may arise. If the alignment is \an2 then the sign will only expand upward. \an5 will make it expand to all sides, but if you're placing this above a jp sign that's already expanding, you'll need to \move up a bit, otherwise the expansion of both signs may bring them too close together or even overlap.

This kind of signs may also use other effects, like the line appears and then slowly gets blurry, or the letters move apart from each other - \fsp.

For illustration, try these things out to get the idea of what you can do (use 720p video AND script resolution):

Dialogue: 0,0:00:20.00,0:00:25.00,Default,,0000,0000,0000,,{\pos(300,300)\bord0\frx0\fry90\t(0,2000,2,\fry0)\org(20,300)}What is this I don't even...

Dialogue: 0,0:00:25.00,0:00:30.00,Default,,0000,0000,0000,,{\an5\fad(1500,0)\move(155,87,1040,670,0,1500)\t(0,1500,\frz-1080)}I still don't...

Dialogue: 0,0:00:30.00,0:00:35.00,Default,,0000,0000,0000,,{\move(400,200,800,200,0,3500)\t(0,4000,\fry720)}What Now?

Dialogue: 0,0:00:35.00,0:00:40.00,Default,,0000,0000,0000,,{\move(1000,450,300,200)\t(\fs120\bord8)\b1\clip(600,10,800,710)\frx14\fry24\frz10}That's enough!

Dialogue: 0,0:00:40.00,0:00:45.00,Default,,0000,0000,0000,,{\b1\org(640,50)\fax1\frz-60\t(\frz60\fax-1)\move(640,630,680,260)\clip(240,85,860,680)}Or is it?!

That's about all I can think of right now, the rest is up to your imagination. Remember though that overusing this will cause lag, so nuke any tags that you don't really need and don't use too high values of the really laggy things like blur or all rotations at the same time.

If there's anything specific that I haven't mentioned anywhere, let me know. « Back to Typesetting Main Typesetting: Creating Gradients

Creating gradients is simple in principle, but since the Gradient Factory script is a bit lame, you can run into some problems.

2015 Note: Nobody uses GradFactory anymore (I hope). Read this part only for understanding how gradients work, and ignore that shitty script. Further down the usage of lyger's 'Gradient everything' script is explained.

First of all, what is a gradient?

Here you can see the color goes from light blue at the top to dark blue at the bottom. The way to replicate this in typesetting is to chop the sign into a large number of clipped lines, each with a different shade of the color, like this:

Here I have one line selected, containing this {\bord0\blur0.6\clip(120,173,411,174)\pos(266,209)\1c&HCE8189&}FLUSTERED You can see that vertically the clip goes from 173 to 174, ie the strip is only one pixel wide. You can do 2- or 3-pixel-wide ones, but of course the more pixels per line, the worse it looks. On the other hand, the less pixels per line, the more lines, and the more lag.

What Gradient Factory does is create all these lines and give each one a different color. With 1 pixel per line this can look great, but may lag, especially with fades and stuff. In this example you would choose the top color, the bottom color, and the width of the clips. The script does the rest... or that's the idea anyway. It has its own problems though. Now how to start: Create your basic typeset and set the sizes and colors in the style rather than with tags. Grad Factory nukes all tags from the line except \pos, and creates the clips and colors, which means all your scaling, borders, and other things will be nuked. You can re-add them later, but for several reasons it's best to have as much set in the style as you can. I commonly create a special style just for the gradient I'm making. One reason for that is that the area of the gradient is calculated from the font size in the style. If the style has \fs20 and your typeset has a tag with \fs50, the gradient will create lines for \fs20 and you're fucked. So I recommend setting the style as close to the required result as you can. In the image above, I picked a font and set the colors, borders, layers and blur. The blue color can be anything, as it will be overwritten by the gradient anyway.

When you have that, run the GradFactory script on the blue layer. Settings are as you see: apply to this one line, vertical gradient, 1 pixel strip, and you only need 2 colors here. If you need more than 3 colors, you use that +colors button. The rest should be self-explanatory. Aside from the font size from the style and nuking the tags, there's another "problem." The gradient isn't calculated for the visible part of the font, but for the whole "box." That means the gradient usually starts a few pixels above the letters and ends a few pixels below, so the start/end colors you selected will be outside the visible range. To compensate for that, you could select a bit lighter color for the top and a bit darker for the bottom. It may take a few tries and personally I find it the most annoying thing about this script. However, this can be bypassed, and the colors can be tuned with the transform script.

Like I said, the GradFactory will nuke other tags than \pos, \clip and colors, so you'll have to re-add things like blur, but when you do, the final typeset will look something like this^.

You may also need fades or whatever, but you get the idea. This is what the gradient will look like in your .ass file:

Each line is a clip with a different color.

As I mentioned before, you can use the transform script to tune the colors. Find the top and bottom VISIBLE lines. You'll find that several of the first and last lines actually aren't visible on screen, because, like I said, the gradient is created for the whole "box" of the font. You can delete the extra lines.

Then you can set exact colors on the first and last line and run the transform script between them. The transform script even allows you to have different color gradients for different letters etc. Refer to the info inside the script for how it works.

Another problem is gradients for typesets with \frz. The gradient will only be vertical or horizontal, but you may want to rotate the actual text. Since the Factory uses only the style for calculations, the clips wouldn't extend across the whole rotated text. The way to bypass it is to change font size in the style to larger, run the gradient, change size back, and rotate the text.

Since larget typesets can mean that you'll have over 100 lines for the gradient, there's a danger of lag. If the typeset is static and has no fade, you can get away with 1-pixel strips. It looks awesome. Fades are a killer so I'd suggest at least 2-pixel strips. [Also the big "!?" was part of the video and is not a rotated typeset gradient.]

Here's one of those \frz ones: You can also do one with multiple colors:

Yeah, this is a bit laggy but... looks pretty good. 2015 Note: Probably wouldn't be laggy today.

Now that I wrote all this... lyger has made creating gradients much easier, so just go here to get the gradient scripts.

Let's just quickly explain how to use "Gradient everything" on this example: 1. Create a regular 2-layer sign with blur and set the primary color to the green at the top. 2. Duplicate the top, green layer. 3. Change the second of the two lines to the blue color at the bottom. 4. Draw a clip around the text on either the green or the blue layer. 5. Select both lines and run the script with these settings:

As you can see, with this script you can use gradient with a lot of things.

Simple gradients are really easy to make: An example with gradients on multiple layers. Aside from the obvious - colors - there's more going on here. There's an extra, white layer, which transforms from alpha 00 to FF both up and down. Since the white is only at the top half, the yellow border needs to be larger there than the red border at the bottom. You can see the border difference on the jp sign too. So there's also a gradient for border on my sign. By the way, you don't have to do all this with the gradient script. Do the basics and use "frame-by-frame transform" to tune the rest.

Gradient by Character

A different version of a gradient is by character, which means you get new tags for each letter. It's a horizontal gradient with only 1-letter precision rather than by pixels, but you can keep everything in one line. Here's an example:

\fax and \fs is gradiented with lyger's 'Gradient by character'; colour is gradiented with my Colorize script. lyger's can do all applicable tags. Colorize only does colours, but it has some extra options, like the HSB gradient used here, instead of RGB. Of course this could then be combined with a vertical gradient.

« Back to Typesetting Main Tracking Signs with Mocha

2015 Note: Even though I've updated this a number of times, stuff keeps evolving and changing. Check this site for torque's updated guide to his Motion script.

What you need to install:

1. Mocha for After Effects. Any version should be fine. You don't need After Effects. 2. Quicktime or Quicktime Lite (Otherwise Mocha won't load mp4) 3. Aegisub-Motion.lua. Place it in \Aegisub\automation\autoload\. 4. x264 8bit-depth (Mocha can't read 10-bit)

Now you need a folder where the clips for mocha will go, and a folder where your x264.exe will be. To make it simple, use the same folder for both. Let's say we'll use D:\mocha\.

Open Aegisub and go to the Automation menu.

First you need to set up the config.

If you chose "D:\mocha\" as your folder, you'll put that in the top part (don't forget the trailing slash), and the same with x264.exe in the middle box, like you see here. I recommend to uncheck the scaling and border/shadow for default settings.

Let's mention what all the options do now, but you can get back to this later.

Sort method: Default - If you apply data to 2 lines, you'll get line 1 tracked first and line 2 tracked after it. Sort method: Time - Resulting lines will be sorted by time.

x, y - track \pos horizontally/vertically. Origin - track \org. Clip - track \clip. Scale - track scaling/zooming, ie. \fscx and \fscy. Border/Shadow - apply scaling to border / shadow. Rotation - track \frz. Blur - scale \blur. Rounding - how many decimals you'll have for each tracked value.

x264 - I never thought of changing this, so... ???

Relative - This is a very useful thing. If it's hard to position the sign on the first frame, you can do so on the last frame and set this to -1. Or you can style the sign on frame 6 and set this to 6, etc.

Linear - this will use \move and \t instead of frame-by-frame tracking.

Delete - Delete the source lines instead of commenting them out. Autocopy - Automatically copy from clipboard into the tracking data box. Copy Filter - Only autocopy if the clipboard appears to contain tracking data. Enable trim GUI - show GUI for trimming. \clip... - This allows you to use different tracking data for clip than for the sign itself. (Remember this. You'll need it one day.)

Let's look at a sign that needs tracking.

This is what it looks like on the first frame...... and this is the last frame.

This is the kind of sign that's easier to track from last frame, and it's better to create it on the last frame too. So we'll start with this: Time to encode a clip for mocha. Click on "Motion Data - Trim" in the automation menu, and it'll just encode without needing any further action.

If it didn't work and it's your first try, restart Aegisub and see if it works after that.

If it worked, then you can skip this next section. In case it doesn't, I'll explain an alternative method for encoding the clips. What sometimes also happens is that the script encodes a clip that looks like this:

You can try encoding from a previous keyframe, but sometimes even that doesn't help, so I use this other method. (Though in some cases the sign is still trackable even on that shitty encode.)

Create mocha.bat with this text in it: x264 --profile baseline --level 1.0 --crf 18 --fps 24000/1001 --seek %2 --frames %3 -o %1_%2.mp4 %1 This is with 23.976 fps, so obviously change if needed. Put the mocha.bat where your premux is, along with x264.exe. Open cmd.exe and navigate to the folder with these files (or open it from there in the first place). Then type this command: mocha seki02_premux.mkv 4193 40

"mocha" is mocha.bat, seki02_premux.mkv is the video you're encoding from, 4193 is the frame where it starts, and it will encode 40 frames. It's actually 33 frames, so you can be precise if you want, but I think you need to encode one more than it shows in Aegisub. I usually just encode more and then move the red marker for the end frame in mocha.

2015 Note: Another encoding alternative is my Encode - Hardsub script. (But hopefully torque's script is reliable now.)

When you have your clip encoded, start up mocha. Open a New Project:

Import a clip:

After you select your clip, hit Enter 4 or 5 times, until all the menus go away. (No need to change anything there.)

NOW YOU TRACK THE SIGN. (I'll get back to the tracking itself in a bit. Let's just finish the whole process now.)

To export the data, you click on "Export Tracking Data",

change the format to "After Effects Transform Data", and click on "Copy to Clipboard".

Move over to Aegisub, Automation menu, Motion Data - Apply. If the data isn't autopasted, paste it into the top box. I explained the options earlier, so check what you need, and click "Go". Tracking data gets applied to the selected lines, and you're done (as long as you didn't fuck anything up). That's all regarding the Aegisub-Motion.lua. Now for Mocha itself...

Tracking a Sign in Mocha

So you have your video loaded. Now you need to create a spline to track.

I'm using an old version, but the new ones shouldn't look all that different. You will mostly be using the icon with an X (9th from left). Next to it is X+, then B, and B+. Click on the one with X, and draw a spline like this:

Now you can start tracking. There are 4 buttons you can use...

Track backwards | Track to previous frame | Track to next frame | Track forwards It doesn't matter in which direction you track or where you start from. We're tracking this one from the last frame, and on the first frame we should end up with something like this:

And when you apply the data, the first and last frame will look like this:

You can try scaling the blur with the script, but it may not match the size scaling, so I usually change the blur by hand. Normally it's only a few frames that need more blur. This sign had some rotations in the middle part, so we track and apply position, scaling, and rotation.

For simple signs, you often don't need anything else than to draw a spline and track. If there's no scaling, things usually work out fine.

If the tracking isn't going well, you have to tweak the options here: Luminance / Autochannel: Luminance follows overall brightness/contrast. Autochannel picks red/green/blue channel, depending on which has more contrast (or so I imagine it works). Usually Luminance works, but then there are signs like this:

The blue sign is jumping around, but so are some other lines in the background, so the whole thing can be pretty messy, and since the blue and purple have about the same brightness, they're not so easy to distinguish, so Luminance would mainly see the black lines, but since there's some black in the background too, it can influence the tracking. If you choose Autochannel, mocha will (I assume) track the blue channel since it shows clear contrast, and the thing you need to track is all blue and the background isn't, so it works out well. I have no way of knowing what mocha actually does track, but for example for this sign switching to Autochannel helped, so I imagine it works something like I explained. But really all you need to know is that if you fail to track a frame correctly and you think it might be related to colours, undo the frame, switch to Autochannel and try again. Either it'll help, or it won't.

Min % Pixels Used: To be honest, I don't remember a single time when changing this value would help me track anything. If your spline is going off, you can try increasing or decreasing this value and see if it helps, but I wouldn't bet on it.

Smoothing Level: I have no idea what this does, and I've never heard anyone mentioning it either, so probably not important.

Translation: This equals position and is always checked. Scale: Check this if the sign gets bigger/smaller, ie. zooms, etc. Rotation: Check this if the sign rotates like what \frz does.

Do NOT check the options you don't need, because they'll only make tracking harder in such a case. The other two options are not applicable for Aegisub, so don't check those.

Large / Small / Manual... I almost never change this. Just know that you can track manually, adjusting the spline on each frame by hand.

Horizontal / Vertical: If a sign makes long-distance jumps, like a fast pan, you may need to increase this value. On the other hand, if the spline jumps off somewhere far when it shouldn't, you can set smaller numbers to limit the area it searches. With simple, continuous, short-distance movement, Auto usually works.

Angle / Zoom: Similarly to previous, use larger values if there's more zooming/rotations, and smaller values if the spline is going too crazy.

That would be the basics of tracking. Now more about what if it's not working and what tricks you can use. If you compare to the previous pictures, you'll see this spline is too small. That means it didn't scale enough, so you need to increase the Zoom value. It should supposedly be in %, but from experience I can tell you that that's bullshit. I have no idea what exactly the number is, but larger value allows for more scaling, so you just have to try. If the difference seems small, try 10. I usually get the results I need with values between 10 and 20. If it's zooming a lot, then maybe 30. You probably won't need to go over that. If you set some value and the spline actually gets bigger than it should, decrease the value bit by bit until it seems right. It's a bit of a trial-and-error game, but after tracking 100 signs you'll have some idea from experience what values you should use. Tweaking the Angle for rotations follows the same principles as Zoom.

Here's an example of what usually happens with zooming signs, and how to fix it.

This is the starting spline. This is what happens with default settings. (1 frame tracked.) You can't see the whole frame, but the sign hasn't really moved much. It just got bigger. Since the spline is jumping far off, we can limit the movement, so I set Horizontal/Vertical to 20. Ctrl+Z; retrack.

And this is what I get. You see it didn't scale enough, but limiting the movement helped to keep the spline in the right area. So I change the zoom to 20. Ctrl+Z; retrack. And here we go. I got exactly what I need.

If a sign changes on one end more than on the other (zoom, blur, etc.), it's probably easier to track it from the end that changes less, and leave the most difficult frames as last. Or, if needed, you can track it from the middle to the ends. This helps for example with fades.

Here you need to track the red sign, moving from right to left... The sign jumps in fast, then moves slowly across the middle, and then jumps out fast again. It moves horizontally a lot, and vertically a little. The first two and last two pictures are the actual first and last two frames. The middle picture is where it slows down for about 10 frames. Tracking from frame 1 to frame 2 would be hard because there isn't much to select on frame 1, so it's better the other way. The main thing about this sign is that you need to set Horizontal to the max - 500, and Vertical to something like 50. Less Horizontal won't track far enough, and more Vertical will only give mocha more useless area to search and possibly confuse it.

Remember those X, X+, B, and B+ icons? The B ones are for Bezier curves. It's usually faster to just make many points with the regular X one, so you can pretty much ignore the Bs. But what about the X+? That one is very useful. It allows you to add another spline to the one you're already tracking, or even replace the old one, like when it starts going off the frame.

Let's say you have a pan like this: You can start with tracking the text on the left. When it starts going off the screen, you use the X+ tool, and draw another spline on the right. You can either continue tracking like that, or you can now select and delete the left spline. (You have to draw the new one first before you can delete the old one.) Alternatively, you can press the Q key (or use the icon on the far right) and move the spline to a new place:

And you can continue tracking.

Another trick for whole-screen pans is this: Set "Link to track" to "None" instead of the layer you're tracking. If it's a whole-screen pan, you can draw the spline anywhere, even over the whole screen. The spline won't move when tracking, but it tracks the pixels moving within it, so when you have very long pans, it tracks them well while the spline stays in place.

Sometimes things are in the way of the tracked sign.

You want to track this sign...

...but it goes under this hand. In this case it actually tracked perfectly well, but that's not always the case. Use the regular X tool to draw a new spline around the hand.

New layers will be placed above the old ones. When they overlap, the bottom layer will not track areas that the top layer is "blocking". If the hand doesn't move, you can click on the middle icon next to "Layer 4" so that it doesn't track the layer. If the hand moves, you can just track with focus on layer 3. Layer 4 will track the hand, and layer 3 will track its spline except the areas covered by layer 4.

If you enable the "Mattes" in this menu, you'll see the splines filled with colours. Layer 3 will track only the red area here. So for example if you have a slow pan of a building with a sign on it that you're tracking, and a car goes by in front of the building, you can make a new spline for the car to make sure the car won't throw off the sign you're tracking.

While we have this menu here, you can also notice the Stabilize button. If you click on that, the focus won't be on the whole frame, but on your sign, so the sign will stay in place while the rest of the stuff moves around it. When you "play" the tracked sign with Stabilize on, you can see more easily how well the spline sticks to the sign.

If a sign lasts 30 frames and moves only on the first 5, you don't need to track the remaining 25 frames. Just track the 5 and apply the data. For the typical Japanese animation where stuff moves only every 3rd frame, you can just track every 3rd frame. This is tracking data that I actually used, since the sign only moved on those 5 frames. No need to flood the script with hundreds of static lines, plus the tracking can actually make the sign wobble a little, while this way you ensure that it doesn't move on the static frames.

While we have this image here... If the sign's duration is shorter than the whole clip, you can move the red markers at the ends to set the start and end for the tracked data. The exported data is only what's between the red markers.

A problem you may run into is that the video loads stretched vertically in mocha, as in, it shows wrong aspect ratio. If that happens, look to the bottom-left part of the screen, and switch from Track to Clip. Then instead of those things like Search Area, you will see other stuff, including this dropdown menu:

Changing to one of those Anamorphic will probably do the trick. If not, just switch to whatever will look right. (Press * to center the screen after.)

That's about all I can think of now. This should explain how it all works. Learning to track difficult signs is largely a matter of experience, so you have to practice. This guide is mainly to explain how to make a video clip, get it into mocha, track a sign, and get the data back to Aegisub, so you should now be able to do that.

For more help, you can check some mocha videos on youtube and other places.

� Back to Typesetting Main Typesetting: Fonts

Seeing from the results produced by typesetting apprentices, choosing fonts seems to be where they tend to fail. The main problem being that they don't really have any fonts aside from whatever is installed by default. So first you need to get at least some small collection of useful fonts.

You can get those from various places. Obviously there are websites with fonts. Here are some you can try out: ufonts | fontspace | azfonts | fontsner | dafont | ffonts You can also extract fonts from decently typeset fansub releases, which is not necessarily as dumb as it sounds, since you'll easily get to a lot of fonts that are actually suitable for anime typesetting. Or you can do what I do - download large font packs from torrents, go though thousands of fonts, delete all the useless ones (yeah, about 80%) and sort the useful ones so that you know what you have and where. Obviously this takes the most time, many hours, but once you get through that, you'll have much easier time finding what you need when you're typesetting. In other words, takes time at first, but saves you time later. If you need a specific kind of font and you alreeady have 300 fonts installed that you have selected as good and suitable, it'll be easy to find one quickly. If you have to go to a website and try looking for something that would work searching by keywords, it might take long and produce poor results.

You can certainly typeset with 20 fonts and get away with it. You will certainly get better results with 400 fonts to choose from. So it depends on what you're aiming for and how dedicated you actually are to this.

Before we get to what to use, let me make a few comments on what NOT to use.

• Do not use 10 MB fonts! It's stupid to increase a 200 MB mkv's size by 10 MB with just a font. Mainly for the reason that just about any 10 MB font can easily be replaced with a 100 KB one. 10 MB fonts are only justified for kanji. My general rule is to not use any fonts over 1 MB. Even fancy decorative fonts are mostly under 1 MB. Larger fonts are simply bloated with crap you don't need. Not that 2 MB would really be a problem, but just get used to checking the size to make sure you don't pick those 12 MB ones for no good reason.

Which leads to another point...

• Avoid system fonts, fonts starting with @, MS, Adobe etc. fonts. Not that they're all bad but those often tend to be the large ones and there are always plenty of similar fonts with sane filesizes. Aegisub has those @ fonts at the top of the list so beginners will choose them 'cause they're first. Just learn to skip those every time you're choosing a font. There are no "good" ones among them anyway, trust me. They're only useful if you need kanji. Even if they happen to "fit" what you need, you can always find a similar one that isn't bloated.

• Avoid notoriously known fonts like Arial, Times New Roman, ComicSans etc. If you use ComicSans, you will break rizon servers because they will get flooded with >ComicSans messages. Times New Roman might work sometimes, but it's obviously a severly overused font and you can always find other ones of that kind. As for Arial, pretty much no japanese sign is so ugly that you could imitate it with Arial.

• Basic Serif & Sans Serif fonts... [if you don't know what that means, look it up] These are not suitable for most signs. Serif fonts may be useful for titles, as seen in the basics chapter. Basic Sans Serif fonts may be useful for cellphone/e-mail messages, or signs that clearly have no decorative elements whatsoever, like the Principal's Office sign here:

You will, however, need something thicker than Arial [even when bolded] for this, usually ones with Black or Blk in the name. For most signs you'll want to avoid these basic fonts, as it will look more like you're "putting text on screen," rather than typesetting. The typesetter's job is to make the signs look good. If we wanted to just put text on screen, we'd do it ourselves instead of hiring you.

So what WILL you actually need? • Some better looking [Sans]Serif fonts That means ones that aren't as plain and ugly as Arial and TNR.

• LOTS of handwriting fonts Handwriting is all over anime so the more of these you'll have, the better.

• Some brush/calligraphy fonts Kanji works great with brushes and the japanese know it. If you want the typesets for such signs to look really good, you'll need some nice fonts of this kind.

• Round/rounded fonts Some with the whole letters being round, some where just the ends are rounded. [Note: Arial Rounded MT Bold looks like shit]

• Square fonts ...when they use square-ish kanji, obviously.

• Chalk/pencil fonts Where would anime be without signs on school blackboards. Better have Eraser Dust and some others ready.

• Fonts with eroded outline Meaning ones where the outline isn't clean but kind of jagged and stuff.

• Some thick/thin/wide/narrow ones This might as well apply to each category separately. Using \fscx150 doesn't exactly produce the same results as having an actually "wide" font. Sometimes extremes are needed to either match the original or to fit the sign where you need it.

• All kinds of deformed and distorted fonts Not needed most of the time, but if you want to do a really good job on a SHAFT show, it'll be useful to have all kinds of things around. Blood splatter, ripple effect, various patterns and textures, lightning and wobbly shapes. No need for beginners but it might still be good to have a few of those around.

• Cartoonish fonts These are quite common, so I'd say have as many as possible.

• Decorative fonts Some script fonts and whatever fancy stuff you can find. It is kind of hard to predict what you will need, but good to have a few lying around.

• Digital looking fonts, ones made out of dots, pixelated ones, typewriter fonts etc.

That about covers the basics. Handwriting is a must, other than that you should have at least 2-3 of each of the other categories. I usually operate with 350-400 fonts installed, though I have at least 10000 at my disposal. [A year and a half later, 900 installed and rising.]

So that's what you need. Some image examples will follow below.

The next step is what to use when. I'd think that would be pretty easy but... I dunno, I've seen enough fails to prove me wrong. So let's start with an example where I let some guys typeset some Nichijou and we used this Shinonome Laboratory sign to experiment with fonts.

The first attempt looked like this. What should be obvious is that the jp sign looks handwritten rather than printed, the outline is kind of free rather than precise, and it has an overlay pattern. Not that you can easily imitate that pattern but it gives you an idea of the general look you should go for. Which means this attempt fails quickly. Besides the font mismatch it is poorly positioned and the color doesn't match either. Second attempt. Improvement in color and position, but not so much in font choice. It doesn't look like a plain typewriter font anymore, ok, but this is still too regular, mathematical, clean, and generally doesn't fit.

Third attempt [I think]. This has some eroded outline so it's not as clean as the previous ones, but clearly it's almost as fat as the average American. That's bad, by the way.

This was, I believe, the last attempt. Eroded outline, some overlay pattern, thickness matches... this passes. I hear this was in the actual Commie release. It... doesn't pass. Low budget typesetting edition, I guess.

The next few are my examples of fonts that could be used...

Oh yeah, I used caps because it seemed to fit better and never tried changing it for the other examples. While you will mostly get the script in capitalized lowercase, there's really no reason why you couldn't use caps for a sign like this. So, this works fine.

This font is squarish, but the thickness matches well enough and it's kinda irregular so... works. This is better. It has more space between letters which makes it look less out of place. Might actually be my favorite of these.

This was just a test to see how Eraser Dust would work. Nothing really great but better than those first attempts up there. Like, if your font collection is poor, this might be ok.

This one's pretty irregular and has lines all over the letters, but is a bit too wild. Still acceptable. In my opinion it's better to make the sign more interesting than more boring. Then again, that should have limits as well. ...just another font that works.

This is again going a bit beyond the original but it's much better than plain boring fonts.

This would be another of my favorites. It has pretty much all the properties of the original and isn't too crazy either. This was actually the first attempt of another guy in my "TS class" and it's fucking awesome. Very unintrusive, maybe the best of all examples.

The larger your font collection is, the more you have to choose from and the higher chance of your final choice being really good. If you only have Arial, TNR, ComicSans and other basic stuff, this sign won't look too good.

This one's from another test. It's too plain and flat. I just changed the font and added a little bit of shadow to make it more 'plastic'.

This doesn't look bad, though it lacks blur, but the red font doesn't correspond with the jp one much. [Also personally I'm a bit tired of chinacat.ttf - the other one] If you have enough fonts, it can make a world of difference. This red one, btw, is what I meant by 'cartoonish fonts.' Stuff like that. 2015 Note: The red colour is clearly wrong. Also, something could be done about the uneven spaces on each side of "idiocy".

We've seen this next sign in the Positioning section, here are two random examples of fonts that would work with this. You may argue that the original sign isn't really as fancy as the typesets, and you may be right, but I'd say... I'd rather make the typeset look better than the original then make it look uglier.

This is something you'll definitely come across. A font that looks fairly plain but is rounded at the ends. This is chinacat again, but here it's just perfect. It's a lot more regular than most handwriting fonts, and here you can match the size and thickness of the letters to the original really well.

Actually no, fuck chinacat. This is how you do it. Children-style handwriting.

One of those chalk fonts. The pattern isn't that visible with small size, but it's good enough. Here on the sides you have one of the not-so-plain sans serif fonts. For the middle sign, the choice of the right font is what will make it look like it belongs there. It has two layers. Both the black and white are slightly blurred to make it look natural. This is quite self-explanatory so nothing to say except... I think the editor made a mistake here! Good thing it hasn't been released yet. Fixed for release.

Does this work? I think it does.

Sometimes you just find exactly the right font...

Now a few things about what plain standard japanese fonts look like, ie where you should use plain fonts too. This is an ordinary sans serif font.

Same here. Adjust the size so that it matches the thickness, match color, add blur, done. Well, almost, 'cause you need \frz and most likely some \fax. Here you have bold serif at the top right and bold sans serif below. No space for the five, so you have to mask it, but you can get quite close to the original.

Simple sans serif bold/heavy/black. The border gets brighter to the right, so you can change the color for each word. Also contains: little bit of \fry and \org, and of course blur and two layers. Ordinary serif font. This one's thinner than usual but since the typeset is much smaller, making it thinner wouldn't look too good here.

This is quite typical serif.

This may be a bit more rounded but I think it's mostly because it's hiragana as opposed to kanji in the previous example.

You could use somethnig more round...

...or something a bit more fancy. But the first one works. Another serif font but this one's thinner and taller. Also play with border/blur/color on the bottom layer until you get it right. You might wanna go for more perfection with episode titles since it's gonna be used in all episodes, so it might as well look really good.

This is kind of half-serif. There are several things you should consider when picking a font for something like this. Height/width and overall thickness. This should be obvious. Then there's the ratio of horizontal vs vertical thickness. You can see the vertical lines are thicker than the horizontal ones. That is usually the case, except for simple sans serif like the "Defence Force" sign above. The ratio may differ though. On the red one just above the difference between horizontal and vertical lines isn't big. But on the "Priestess..." higher up the horizontal lines are really thin. Here on the "Vanishing Children" there are no really thin lines. So you should try to match that. The last thing that will determine whether the font matches or not is the ends of the letters. This of course makes the serif/sans difference. The serif endings may vary a lot though. They can be sharp/pointy in various ways [Garamond, Footlight, Timpani], less so like Raleigh [Gregorian Calendar sign above] or Arno Pro, rounded [Cooper, Souvenir], short/heavy/square-ish [Acclaim, Albertus, Strayhorn, GeoSlab(numbers)] or with some decorations [Zapf Chancery]. So here for "Vanishing Children" you need serif where the endings aren't too pronounced and horizontal lines are thinner than vertical but not too thin. I think the choice here is pretty much perfect. A few typical serif examples if you have no clue what to look for: [Not Times New Roman], Garamond, Goudy Old Style, New Baskerville, Georgia, Thames, Palatino

A few typical sans serif examples: [Not Arial], Franklin Gothic, Myriad Pro, Zurich, Frutiger, Liberation Sans

This font is very "open," with a lot of space between the lines and [almost] the same thickness of all lines. A characteristic feature here is that the zero has the same width as height. Here the japanese is a bit closer to handwriting but handwriting or brush fonts would be too messy. The font I used has that varying thickness and the ends of letters fit pretty well.

This shouldn't need any comment. I like typesetting Shinsekai Yori because it has new challenges every episode. These are titles from episodes 1 and 2. And this is from episode 3. I have no idea how all these moonrunes are even readable.

A few examples from Muromi-san:

Just imagine how bad they would look with Arial...

« Back to Typesetting Main Typesetting: Blending In

Ultimately what you want to achieve is to make your typesets blend in. To make them look like they belong there. If possible, to make them look like they were there in the first place, so that when somebody watches the episode, they don't think "oh, somebody put text here to tell me what this sign means." Ideally the viewer shouldn't even notice something was added. The typeset shouldn't stand out, being the first thing you notice because it looks out of place.

Making it blend in prefectly is not always possible, so sometimes you just need to make sure it doesn't look distractive/odd/ugly.

The way to achieve this is by combining all the things you've learned in the previous chapters. Choose a good font, find a reasonable position for the sign, make sure to get all the colors, borders and shadows right, align it correctly, use as much blur as needed to make it look natural, etc.

Sometimes there are several reasonable ways you can typeset a sign, so choosing the best one will make a difference too. [Sometimes that requires thinking outside the box a bit.]

For example this one...

You could try to squeeze the typeset somewhere around the jp letters, or put it outside the box, but that would pose obvious problems. The thing you can make use of here is that the box is a regular rectangle and the background is one solid color. Which means it will be extremely easy to just replace the sign without any interference. I didn't even use drawing mode here, just 'opaque box' for border. 2015 Note: But that's dumb, so don't do that. It was a long time ago.

Here you need to pick the right font and match the size and thickness of the letters. [And obviously have no border/shadow.] At first glance you'll hardly notice anything was added here. Looking at it some more, I'd say it could use a little bit more blur, and a little bit of alpha or slightly darker color. Here the challenge is aligning and positioning. Forget trying to squeeze it next to the jp or rotate it ~90 degrees [in relation to current orientation]. Just use a place where it comfortably fits and looks like it might be the title of the paper. Align correctly in both directions and add some blur to avoid jagged outline. Also make sure you're not using pure black just 'cause your style has it. Almost nothing in anime is pure black. Match colors precisely.

Use good font, match color, add blur. Now imagine Arial with some stupid white outline and no blur, like some derps would do. Nothing much inventive here, just follow the basic guidelines and imitate the original sign. Use a somewhat square-ish, thick font. There are 3 lines [in script], each of them has 2 layers. Top layer has matching color [red-ish/blue] and a shadow [no border]. Match the shadow distance. Match the transparency/color of the shadow, not just black "cuz it was there". Bottom layer has white outline and another shadow, this one with a lot more transparency. Since the whole trick here is to really just get the basics right, make sure you notice all the borders and shadows correctly. I almost missed the second shadow here. Sometimes you'll need more than 2 layers to get it all done right.

The 4 corners... would be hard or pretty much impossible to match, since there are multiple effects, embossing, texture etc. So I decided to kind of approximate the colors and at least make it look nice. Which means mainly using a nice font. Add shadow for at least some sense of depth, and play with the shadow/border till it looks decent enough. Since you can't match this exactly, there would be plenty of variations of how you could do this. IMO you're better off using some nice script font even if it doesn't quite match, than using TimesNR which might technically be closer but would look like shit.

As for the Flashback... the font is the easy part. More interesting is the shadow. And even more interesting is that the sign was moving around earthquake style, changing the direction of the shadow on every frame. So what I did was to time it frame by frame, use \xshad and \yshad on each frame to change the shadow direction, and adjust \pos for each frame to folow the shaking movement of the jp sign. Good thing was it only took 20 frames. [If you're learning, you're not required to follow the movement & shadow frenzy. Just saying it can be done and how.]

The ones on the sides obviously can't blend in perfectly. There's hardly any space for them anywhere else, so the best you can do is to match everything as closely as possible. This isn't even as close as possible but it's good enough. The other two can be done much better. Positioning is pretty obvious, so good font, thickness of letters etc., correct colors and a bit of blur. The top one has even that background blur [you add very little border, like 0.1-0.2, a lot of blur [4-6], and adjust color or transparency till it looks good]. The bottom one doesn't have that because I was too lazy. Negtive points for me. The ones on the sides also need more blur. Four typesets here. The Zoom would look much better with another, top layer without border/shadow and with some blur. However the whole sign was moving and increasing in size, which means \move and \t, so adding another layer and more blur might munch on the CPU too much, so I left it like this, especially since there are 3 other signs in the picture. The 'Math' doesn't look too great, but font is ok and it doesn't stand out too much, so it's good enough. You could replace the letters on the yellow background with the 'Math,' but I don't like replacing signs if I can't replace ALL of them. Having some kanji replaced with English while there are other kanji in the picture looks lame to me [though I wouldn't say it's 'wrong']. Third one is 'Mathematics Drill.' The big blue letter covering the Drill is moving away till it's not covering the word at all. So I used \iclip to remove what was needed from the Drill, and changed the \iclip frame by frame. [Well, I was cheating, so each 2 frames.] Fourth one is the Winning Lawsuit. Didn't really give me many options as to what to do with it.

2015 Note: In retrospect, this whole sign looks pretty bad and could be much improved, and without lag as of now.

As you can see, I used 4 different fonts here, trying to match the style, thickness, size etc. [as much as possible] It should be clear that all of the signs use blur. The Lawsuit could use a bit more, but then it was getting hard to read. If you can't see the sign, it's blending in well.

I dunno, I just thought this was a cool idea... It's a typeset for the white-on-pink sign at the top. Clearly there wasn't enough space there, so... Speaking of having some imagination... an example of how you don't always have to stick the typeset right next to the jp.

This is perfectly readable & looks good. Two layers for blur, of course, and changing colors letter by letter for the "Question." Spikes? How? Think about it... there must be some spiky symbols you could put under the letters...

All in one line [but 2 layers for blur]. Different \frz for each letter. This is the kind of sign that makes most typesetters go "Oh shit [am I really gonna have to typeset this?]" But it's really not that bad [unless it appears 20 times from different angles]. This only appeared 4-5 times. The one on the right has extra layers for that glow [though less glow than the jp - \blur15 on several signs at once might not be so good].

Here's one more in the evening. I've done so many Kitano Tenmangu Shrines that I couldn't count them. Here's 3 in one picture. Aside from aligning & blurring, the middle one has some extra features in order to blend in. I tried doing it including the semi-hidden part, and it happened to work really well. The trick is pretty much this: {\alpha&HF0}Kitano Tenma{\alpha&H00}ngu Shrine Btw nobody tells you it should go at the top of the shrine. You just know you're supposed to typeset the white kanji. So most people would try to put it somewhere around the kanji. Like I said, don't do that. Find a less retarded / more elegant solution.

This may look simple enough but... if you do just one layer...... it looks like this. See the difference? So yeah, another layer, different color, a lot more blur.

On the right... First choose a font. Then add some \fry and place \org about a mile away to get the alignment you need. [OK, maybe not that far] That may take a while to get right. Adjust font size and position till it fits. Use \fax for fine tuning. Add blur, obviously. Then the colors... The left side is brighter than the right. So I used 3 shades for each line. The whole thing looks like this:

0:02:54.02,0:02:56.07,names,Caption,0000,0000,0000,,{\an7\fs34\fax0.04\fscy110\bord0.5\blur0.8\frx6\fry18\org(839,950)\frz6.258\pos(800,-45) \alpha&H80\c&H464036&}High Schoo{\alpha&H75}l {\alpha&H70}Nat{\alpha&H60}ional \N{\alpha&H80}Ogura Hu{\alpha&H75}nd{\alpha&H70}red {\alpha&H60}Poets \N{\alpha&H80}Karuta {\alpha&H75}Ch{\alpha&H70}ampion{\alpha&H60}sh{\alpha&H55}ip Omi Learning Center was even more interesting. Font, positioning and aligning is obvious. I don't even remember but apparently \frz\fry\fax. The background changes color/brightness a lot from left to right. That was the biggest challenge. \alpha probably helped with a bit of that. Most of it was done by changing shades for main + outline color for almost each letter. It also has both border and shadow. There's \yshad on top of that, which actually created a bit of an effect that I didn't even expect, where the top edge of the letters looks brighter. I'm not even sure why that happened but it was a nice bonus. Whole thing here:

0:02:54.02,0:02:56.07,Chiha-title,Caption,0000,0000,0000,,{\fs45\shad1\yshad1.5\blur1\fax1\b1\alpha99\frx0\fry9\c&H928C7B&\3c&H595445& \4c&H2E2D27&\frz338.838\pos(459,408)}O{\alpha90}mi {\alpha80\c&H96927C&}Le{\c&H8B8E73&}ar{\c&H767A65&}ning {\c&H666851&}C{\c&H5C5B45& \3c&H4B4637&}e{\c&H515038&\3c&H423D2F&}n{\c&H48472F&\3c&H353024&}t{\c&H3E3D2B&\3c&H2C271C&}e{\c&H393826&\3c&H241F16&}r

So yeah, sometimes it takes a bit longer to make it look good.

Signs like this are awesome. They're not moving, so you don't have to chase them across the screen, but you get to have fun with the details. So how do you imitate all the borders/shadows? 5 layers. Layer 5 - just the green letters without border. Increase letter spacing to avoid too much overlapping of the letters. Layer 4 - \xshad-3\yshad-3 with the light color. Layer 3 - \shad3 with the darker green. Layer 2 - \bord3 with the same color as Layer 5 - to fill in the corners. Layer 1 - \bord6.5 in dark grey and \shad5 in dark green. ...and of course blur on everything.

Similarly here you have a date with an outline but also a 'light reflection' on the left and at the top. Pick a simple sans serif font that will roughly match the thickness of the letters. Use layers with \xshad \yshad, or just an extra layer shifted a pixel up and left. Fairly simple. On the other hand, you don't really have to match this sign, in my opinion. If you do something else that doesn't look out of place, it should work. If someone says you're a retard because the sign doesn't match [I've seen that happen], he's just being dumb himself. It seems unnecessary to me to "match" something that isn't actually on the screen [because you masked it].

Another fun sign [Maria Holic is full of them]. The cool thing here is that it's all done in 1 line. All you need is one \N, different \frz for each word, and move \org somewhere far. Additionally the words are moving back and forth every 2-3 frames, so I timed frame by frame and changed \pos by 1 pixel back and forth. While 1 pixel isn't much normally, the effect when you have \org somewhere far is pretty cool with all those \frzs. One hour of work. 39 lines. Just because I can. [Still not perfect, though.] Original line in script was "Misakichou 3-3". I decided while I'm at it, I might as well do the rest. All signs are in 2 layers, to get blur on both the outline and primary color. Split to make a line for each ~2 letters. Positioning, rotating, \fax-ing. Takes a while to get it right [and the top one is still wrong]. For the top one I also had to change color for each letter [and outline] because the background darkens a lot from left to right. Nothing should stand out as obviously added.

« Back to Typesetting Main How to Use ASSDraw

ASSDraw can be pretty useful for masking, but also for actually drawing some fun things, if you're a typesetter with imagination.

Most of the time for masking this will be enough: {\p1}m 0 0 l 100 0 100 100 0 100 You'll use the scaling tool to stretch it and mask anything with roughly rectangular shape. If you add border, like \bord20, you can have round edges too. If you scale it down to 1 pixel and add border, you'll even have a circle. I'm sure you now understand how to make an ellipse, which you can then rotate etc. So for most masks you won't need ASSDraw. But what if you do...

I'll use a funny example from a recent episode. It's a bit unusual way of using ASSDraw, but it will do for this guide.

So I was supposed to typeset this sign. I'm thinking "What the fuck am I supposed to do with this? There's no space for the typeset unless I make it so small that it will be unreadable. I can't mask it either unless I wanna pull some retarded hadena stunt that would forever ruin what little reputation Commie has left. It's even fading out just to make it more complicated. What now?"

After considering my options for a minute I decided that there's no point in trying to do the impossible. It's either \an8 or I have to come up with some unusual idea. And I really don't like \an8. I don't think I've ever done that. So I decided for ASSDraw.

Here's how you use it:

1. Open it. [Duh] 2. Drag the zoom slider [under 'Background' in menu] all the way to the left. 3. In Aegi [on a frame where you wanna use the mask] right click on the screen and select "Copy image to Clipboard". 4. In ASSDraw go to Canvas -> Paste [or Ctrl+V]. You can set the opacity to 100 - better in most cases. 5. At coordinates 0,0 there's a red dot [actually a cross if you zoom in]. Right-click and drag it around. Put it where you want the drag point of your drawing to be [the anchor in aegisub] 6. Last 2 icons in the menu are 'Pan drawing' and 'Pan background'. First one should always be on. The second one you'll turn on after step 5 [ie now]. 7. Draw stuff. 8. Copypaste the final coordinates to aegisub. It's easy once you go through the process for the first time. The pain in the ASS for beginners is to figure out how to get the result in correct zoom and how to not have the anchor point 2000 pixels away from the actual drawing. That's what points 2, 5 and 6 are about.

Now for the actual drawing. You could probably figure it out by yourself.

That red dot/cross is the starting point. You can either work from there, or move the point, or create a new one. At the bottom of ASSDraw you can see "m 0 0" That's the starting point. If you start from there, it will also be the anchor point in Aegi. If you want the anchor point in the center of your drawing, move the starting point or create a new one. Select Drag from the tools to move points. Select Move to create a new one. If you make a new one, delete the "m 0 0" from the line below.

You have a starting point so now you want lines. Obviously you'll choose Line from the tools and start drawing. Kinda like clipping in Aegi. Since you have 'Pan background' enabled, you can now zoom in [with the slider or mouse wheel] and move the screen with right click or arrows as much as you want. Just place dots with the Line tool till you have what you wanted to draw. Alternately there's the Bezier tool, just like with clipping in Aegi. Simple enough.

A little demonstration:

This is ASSDraw. The red cross is the center. The line is "m 0 -11 l 11 -4 l 12 7 l 3 14 l -8 12 l -13 1 l -9 -7" Copypaste it to Aegi with these tags: {p1\an7}m 0 -11 l 11 -4 l 12 7 l 3 14 l -8 12 l -13 1 l -9 -7 Then position it where you want it to be.

You'll end up with this. The point of aligning the red cross and using \an7 is that you have the anchor point where you want it to be. If you don't do that, things will still work, but your anchor point for this little drawing can end up on any place on the screen or even outside of it, which is a bit inconvenient.

So you draw what you actually want... 2 straight lines and the rest is Bezier. You can paste it in Aegi and see how it looks, then fix it some more and just paste the new line over again.

This is the result, in 2 layers, one with border, set colors and some transparency.

Now you just add the styled text and you're done. So that's how you typeset a sign like that. It may still be a bit ridiculous, but if you can typeset this and make it look better than this, please show. Also, the "welcame" is intentional, so you can stop laughing.

How to make hollow shapes?

One thing that may be hard to figure out is how to create an empty space inside a drawing.

Each of the two shapes starts with m. The trick is that they have to be drawn in opposite directions. If you draw them in the same direction, you'll get this: For illustration of what can be done with this, check this thing made by KKRais.

And another example of what I did recently:

I couldn't find a font that I'd be satisfied with and wanted to try some drawing anyway, so I drew the whole title.

Sometimes people give me ideas...

< Haidaraaaaa > there needs to be an optional assdraw test. < reanimated > if optional, you can just make one for yourself < reanimated > like "draw an elephant with ASSDraw" < reanimated > there you go < reanimated > actually lemme try that So I did.

In the meantime Haidaraaaaa tried a cute elephant but got something out of a nightmare instead...

You can use ASSDraw to make additions to your text, like I did with the M in Muromi here: ASSDraw has no saving option and some bug when using Ctrl+Z that can mess things up pretty bad, so make sure to back up your drawing coordinates every now and then.

2015 Note: ASSDraw does have a saving option.

« Back to Typesetting Main Typesetting - Examples

Here I'll be posting some examples of typesets that I've found in various releases. Some of them not too bad but could easily be better, some of them pretty bad for no good reason.

I'll be updating this page as I find new interesting things.

This is dickpants typesetting 101. JP sign: large thick "serif" (not sure how to define that in kanji but should be close enough) font, no border, has shadow. "Typeset:" small thin sans serif font, no shadow, fucking black border... Question: Why?

I can't believe this was actually released. No matter how little you know about typesetting, you should be albe to handle borders and shadows. Font size is pretty easy as well.

Takes 1 minute to fix, out of which 40 seconds will be looking for a good font...

How hard can this be? ^

Same here...

Might be good to use a font with dots, nuke that retarded border, add a little blur...

There you go. Viewers might as well think the studio used both japanese and english title. Even if you don't have a font with dots, it would still be common sense to at least type \bord0.

I don't even...

90 degrees rotation successful, yes, but that's about it. Well, I'll give credit for the font on the left, that works (but not like this). Other than that it's "Herp derp. Let's put some text over here... bam! Finished."

Left sign: Text is black, so keep it that way, and place it somewhere where it doesn't overlap with other things.

Right sign: If you have to choose between placing the TS a bit further from the sign with smaller font, and full retard mode, please don't choose full retard mode.

This is still all very easy. Nuke (something that looks totally like) Arial, nuke border where it shouldn't be, don't use colors that aren't even on the screen, add a bit of blur. Oh and don't place typesets "over" the signs, duh. This "let's put it as close to the original sign as possible" strategy is pretty dumb. Muru Muru tries harder and so does the typesetter. Too bad he still fails. The colors kind of match, mostly, so that's an improvement. Font is not Arial so that's another. Aside from the one in the red box though, the font still sucks. Using a very round font for squarish signs is not the brightest choice. The next obvious thing is the border. The jp signs have a very thick one so why use thin? Probably the most awesome thing though... watch how the "Future Diary" casts a sharp bright shadow over dark things. Amazing, huh? So, dear typesetting students, if you want a shadow that isn't too dark, you don't do it by choosing grey color. You do it by increasing transparency. (\4a&H90 or whatever value works) And use the damn \blur tag. So, pick better fonts, fix the borders and shadows and it looks a lot less fake. This is still far from great, but it's far better than the one above and took only a few minutes to change. The bottom sign could be much better, but since there are already 3 signs on the screen, I didn't want to add too many layers with blur, since it might lag.

Not bad, generally fits in but...... font and one extra layer and you're much closer. The middle layer has dark inner color and thin bright border and is positioned a bit up and left. Bottom layer is just one color and \blur5.

Here you obviously have a problem with alignment and color doesn't match. This one's much better on both counts, but still has issues. The alignment is still a bit off, but few people would notice. Main problem is readability, because of the color, especially for 'Lamp'. Let's explain one thing here... I'm sure the color was matched with eyedropper, but that doesn't always work. The reason is difference in background brightness. The typeset is on darker background than most of the jp sign. The darker the background, the darker the font has to be. You can check with eyedropper that the jp signs follow that pattern. Otherwise visibility goes down when the brightness is similar for both, even if it's a different color.

So if I fix the alignment and adjust the colors, it gets another notch better. As it often is in such cases, the color changes a few times throughout the line, because so does the background. Here, have some Doki quality. Apart from the crappy translation [no, I can't read moonrunes, but I'm told by translators this is like google translate], this is typical Doki typesetting. They kinda know how to do it, but not really. They can use rotations, but do it wrong. See chapter on aligning to learn how to do it right. Doki can always be beaten by experts from Hadena. This group is a true legend. No one else can fuck up translating, editing, timing, typesetting and encoding [let's not even mention QC here] with such magnificence. All of it apparently despite people from other groups trying to help them. So what in the bloody hell is this? - They decided to use a mask. - Failed to match the outline of the sign. - Failed to cover the bottom of the sign. - Failed to use blur on it, making it jagged as a chainsaw. - Used a bit of transparency, so that you can see the kanji underneath, for whatever unknown reason. - Used a FUCKING SHADOW on the mask! - Put text on it. - Each word going in different direction.

I. Don't. Even. Hello. This is Hadena again. We can put text on screen, see? What? What is blur? We don't heared about this. But sharper is better quality! What, shadow? We can has no shadow? Oh. But. It look more proffessionull with shadow, no?

OK, anyway...

Here's some other derps. Clearly they don't have what it takes to be a legend like Hadena. So they might as well try a bit harder and produce something decent maybe. They could start by matching the color right. I know most people wouldn't bother with 2 layers but... it looks so much better if you can blur the red as well. Oh well... Scary indeed! Beware of brazillian typesetters! [I dunno, just guessing...] Same guys as previous sign. Maybe they should go for a legend after all. This is pretty... scary. Seeing that, somehow I'm not worried about molesters at all. There's something more sinister lurking around. Also the main font with purple shadow... But guess what? Beware some more, because Hadena is back, motherfuckers! Typesets attacking you from unexpected angles, right about to drop a few child molesters on your head as they fly over you. 2015 Note: I have to wonder about the processes occurring in the head of the person(s) who thought that this was a good angle.

Honestly, the best group for this sign was HorribleSubs with \a6. I thought the idea of fansubs was to improve the quality of crunchyroll, not make it worse. But what do I know.

Here's a bunch of groups doing the same sign:

This looks ok, though the color (and blur/glow) is a bit off.

This color is also off and it's missing blur on top of that. Color is off. I think this was herkz, who likes to laugh at others for exactly this, so feel free to laugh at him. Also it's only like 1 pixel away from the jp, which is pretty dumb, since there's plenty of space below.

Finally somebody got closer to the color, best so far. Here the color is off again. I don't know what everyone's problem here is. Also seems to lack blur.

Yeah, I know what your question is. The answer is: asuka subs. What can I say? Nope. It's true that replacing the jp seems like a good idea here since nothing's in the way. But first, this is not the kind of font you'd have on a cell phone. And more importantly, if you can't match the color of the background, just find another job.

Yep. Good enough, but could be better. The end should be leaning a lot more to the right, you can clearly see the edges of the mask, etc. You know, if you just put \blur2 on the mask, it'll do the trick in many, if not most, cases.

Uh-huh. Ok. You were saying you can typeset? I see. Where did you get that idea? I don't even...

Here's something fun I did recently:

What do you need to do this? First, a fitting font. Check. Now, there's not enough space on the sign to put the English next to the jp, so... masking. Make a regular square mask, scale it to the size of the sign. Use rotation and \fax or \fay to make it fit. Use the clipping tool to cut the mask so that the yellow thing is not masked. The main problem is, you can't mask the letters without masking the circles too. You could just ignore the circles and pretend like they weren't there but... we're no amateurs here, right? So we make circles. Make letter o or O, scale it up, blur, set alpha to ~H80 [so that you can make the circles overlap], set color. Use clip to limit the blue circle to the sign only. Now the letters... color, blur, rotations and \fax to align the sign properly. Set the layers correctly... mask is 0, circles are 1 and 2, letters are 3. Done. Let's take this opportunity to mention something else here. This is not really a case of bad typesetting per se. What happened here is these guys muxed in more than 36 MB of fonts. That's not only pretty retarded but also doesn't exactly work, as you can see. It's a limit in haali splitter or something. If you go over 36 MB with attachments, it only takes whatever 36 MB it grabs first and the rest is ignored. So if you stuff your release with a few 10 MB large fonts, you may end up with a bunch of fonts missing for playback and some terrible typesetting. I was using 31 fonts for Acchi Kocchi, and they were 1.3 MB all together, so I don't really get what's with this bloatcrap in many groups' releases. Also realize that somebody probably spent like half an hour typesetting this, and this is what the viewers saw in the end. While we're at this, also don't use mkvmerge over 5.1.1 [I think] because that causes some more problems with otf fonts. In fact, stick to mkvkerge 4.1.1 [or if higher, check the "disable header removal compression bla bla" option].

So that was plenty of examples of the wrong stuff. Now I should probably add some more of the good ones. I just typeset this Acchi Kocchi extra episode so here goes...

[In the light of the previous screenshot: I used 30 fonts for this episode, total size: 1.8 MB. Hell, even all the fonts I used for all the 7 Maria episodes I typeset were 3 MB in total (about 55 fonts).]

This would be the very basics: 1. appropriate font 2. correct colors 3. reasonable font size + border size 4. right amount of blur in 2 layers

It shouldn't be apparent that it's not a part of the video. This is really easy, so please don't fuck up signs like these.

I would add the spikes if it wasn't moving but... it is, so meh.

Some clipping here. If you're gonna clip your sign, you'd better do it right, and not like some hadena or doki derps. Shadows shouldn't be difficult either. Use \xshad & \yshad if the shadow needs a different direction. This one has only \yshad, for example.

Two borders are also easy. You just need 3 layers instead of 2.

Choosing the right font can help a lot. Using blur isn't really that difficult either. I don't know why more than half of the groups out there keep failing at this. I really wanted to imitate the dotted outline here without too much effort [like ASSDrawing all of it]. So I used a font I had 2 versions of - a clean one and an eroded one [you can also see that one below]. I made the top layer with the clean one and the bottom layer with the eroded one. I didn't actually use border so i just playerd with the font size, scaling and spacing till it looked like this. It's not perfect but at least it's going in the right direction. Considering that it wasn't much effort I'd say it's decent.

This is kinda hard to reproduce because it has a lot of colors and a lot of blur and some transparency... but it didn't end up looking too bad, all things considered. Sometimes masking is obviously the better option, because you don't wanna use \fs15 and make it look bad and unreadable at the same time.

Here masking didn't work because there are like 100 shades of blue [and it moves], but the 'Skate Rental' looks pretty good like this. The 'Ice Arena' not so much, but I didn't wanna make 8 lines that are moving, so this was about the best I could do in one line. Not sure how much lag would doing each letter separately cause, but I didn't feel like trying. It would also be possible to use \org to align the letters better and mocha it but... sounds like a lot of pain, doesn't it. Besides, with the perspective here I'm not even sure what the letters should really look like [though doing each one separately would certainly improve it]. I think the main thing is to not make it stand out so that by just glancing at the picture you don't notice there's something too obviously weird.

I haven't really seen much of this outside of Acchi Kocchi, but this is the case where the sign zooms out with the first few frames zooming really quickly. Usually about 4-5 frames go from almost full screen to almost the final size, and then it goes slowly from there. Trying to mocha the first 4 frames can be near impossible and certainly frustrating, so I usually mocha from wherever it seems reasonable. After applying the mocha data I do the first 4 frames by hand - or if I feel like mocha could handle it, I track it all and fix the first few by hand. Mocha may handle the size and position fine, but at least the blur needs to be adjusted manually on the first frames. Then, after all the work you've done... nobody will notice those few frames. Welcome to the world of typesetting. Then again if they notice nothing, it's a lot better than if they notice how much you suck.

This is like 22 lines in the script [6 of them for the topmost typeset + layers for most others]. Thankfully this was at least static. But since it is static, you can play with the details without worrying about lag. It's a lot of stuff in one frame but this is actually low-stress typesetting when you know you don't have to make everything move. You can pretty easily make this look good just with the basics - fonts, sizes, colors... some rotations and that fucking blur! Your main goal here is to make it all blend in. Like if you show it randomly to somebody who doesn't know what's going on, they shouldn't immediately go "LOL, who put these horrible captions over this?" Layers, borders, blur, frz, fax, semitransparency on some layers... and a pretty good fade to white.

More to come... 2015 Note: Probably not.

« Back to Typesetting Main Typesetting Faster

A few tips for how to work faster when you have a lot of signs [SHAFT shows, Acchi Kocchi etc.].

There are usually 2 ways you'll get your signs for typesetting: either in an already timed script [which may be timed to your raw by the timer or timed to CR raw by CR] or on the pad.

If it's CR, you start at the fine timing part. If it's on a pad, you're starting from scratch. Signs on the pad usually look something like this:

{3:33} When he gets here... {3:37} I'll bite his head off. {3;45} Commence the Attack! {3:45} Second Victim

If there are 80+ signs, it's really good to have a working system. From experience I can tell you that after 8 hours it gets pretty tedious and tiring. The most I did in one go was 12 hours and the last 3-4 were pretty... meh. So here's what I do.

1. copypaste signs into Aegi

2. round 1 - rough timing

This has fortunately become easier with Aegi 3.0. Select the first line. Timecode says 3:33. Click in the Start Time field and type 3:33. Hit Enter. In 2.1.9 this didn't do shit without also setting End Time. In 3.0 it not only does set the time [for both start/end] but also jumps to next line. Look at next timecode [right under the Start Time field where you're typing], type it, hit Enter. This will time all signs to one frame based on the timecodes from the pad. Good enough for rough timing and it's fast. Makes no difference that it's only timed to 1 frame because you need to fine time it anyway.

Update: now you can forget about that and just use , which gets this round down to 2 seconds.

3. round 2 - fine timing

This is unfortunatelly a lot slower since you need to find the exact start/end frames. Basically you click on each line, left arrow back to find the start, set start, right arrow to the end, set end. Probably the only thing that helps you here is that many signs start/end on keyframes. If you can see on the audio track that it's likely to be the case, or you know from previous episodes which signs tend to be like that, you can just click on the yellow icon [between the ones for start/end frame and the one for shifting] which will set start to previous keyframe and end to next keyframe. [The "Time Signs" script will also snap to nearby keyframes.] Other than that this is pretty slow, but again, at least much better in 3.0 because of much faster seeking. While encoding keyframes is usually something timers do, it's useful to have keyframes for timing signs as well, especially when you have a lot of signs, since many if not most signs start/end at keyframes.

4. add blur

Use a script to add blur to all lines. You're gonna need it on all lines anyway and doing it one by one steals your time. This takes 2 seconds and makes sure you don't forget to add it to any line. At this point you may wanna get rid of those comments with timecodes and stuff - {TS 5:35}. For that, and other things, you can use this script. 2015 Note: Time signs can now add blur and nuke the comments automatically.

5. round 3 - set colors

Next round of going through the signs you use the eyedropper and set correct primary/outline[/shadow] colors for each line. While you could do other tasks along with that, I recommend doing this alone as it involves just mouse clicking and no typing so it can go pretty fast like that. 2015 Note: I don't really do that anymore, but it's up to you. 6. round 4 - typesetting

At this point it's probably no use splitting the tasks any more. For the easy signs you pick a style [that includes a font], set font size [or just using the scaling tool as it's faster than typing numbers], set border size, set position, use Blur and Glow in most cases, and you're pretty much done. These days you can use scripts for just about any task, so do it. You may want to do all mocha tracking in one batch or leave other specific kinds of signs last or do them first. I actually prefer to go sign by sign with each of them being different, as I get bored with too much repetition.

7. round 5 - checking

When you're done with everything [and have sorted the script by time], you seriously need to make one more round, click on each sign, go one frame back to make sure it doesn't appear too late, right arrow to the end to make sure it doesn't end late or early and that nothing breaks in between [mocha, layers, movement start/end times, \t tags, nobody walks in front of the sign etc.]. This script can help you do it faster.

Use automation scripts. There are now scripts for almost anything you can think of, so you pretty much don't need to type any tags. Get familiar with all the scripts that are available.

Know your keyboard shortcuts, like ctrl+D for frame-by-framing by hand. Know that in Aegi 3.0 you can drag multiple signs on the screen at once, therefore you can frame-by-frame 5 signs on the screen all at once quite easily. The other tools can be used for multiple sigs at once too - rotation, scaling.

Bind your automation scripts to hotkeys. This will improve your speed a lot. Go to Preferences-Interface-Hotkeys. I prefer to set the shortcuts under "Default," but be aware that these will work even when typing in the Subtitle Edit Box, so don't make shortcuts that you might actually need for typing, like Shift+letter. If you want such shortcuts, instead of Default put them under Subtitle Grid, since that's where you'll usually be. Add a hotkey you want, and under "Command" type "auto" and aegi will autocomplete and give you a list of the available scripts, so just choose the right one from there. With Ctrl+letter, Alt+letter, or any combinations of Ctrl/Alt/Shift+letter you have tons of options, so you can usually assign scripts to logical letters, like Alt+B for Bold, Alt+I for Italics, Alt+8 for adding \an8, etc. For some scripts I just set a single letter under Subtitle Grid.

Modify your scripts. If you see you're doing a specific task often, and there's theoretically a way to make it faster or do it in fewer steps, modify an automation script or write your own. If you're using my scripts and think something could be improved about them, let me know and i'll upgrade the script if it's reasonably doable.

If you have a sign that repeats itself every episode [titles and such] that needs mocha tracking, don't track it every time but copy from previous ep and shift. Even titles that don't move but just have a lot of tags are faster to copy from last ep and shift than redoing them every time [actually I hope nobody's doing that]. In fact, what I usually do is open last ep's TS file, save under new episode name, and delete all the non-repeating stuff, thus leaving titles, eyecatches, and whatever else repeats itself every episode. Then I shift the signs to where they should be this episode and see if they need any modifications. Eyecatches tend to change position on the screen, ep titles may change colors, fades, etc.

If you're using a lot of styles, it might be good to name them so that you can easily tell them apart, and not just sign1, sign 2 etc. You can use the actor field for some notes regarding what you need to do later so you don't forget. For example I sometimes type "mocha" in there to make sure I don't forget that I need to track that sign later. I hope you know Aegi has 3 modes of displaying tags [the last icon in the top toolbar cycles through them] and that you're using the one that shows whole tags in the script.

It also improves your speed if you don't check IRC every 2 minutes, by the way.

« Back to Typesetting Main Creating Lua Automation Scripts for Aegisub

written by lyger

Introduction

(last revision: 2013-06-05)

This tutorial is meant to serve as a basic guide to creating Lua automations for use in Aegisub. If you’ve worked on an advanced script, especially if you’re a typesetter, you’ve probably encountered tasks that are tedious, repetitive, and/or require more calculations than you’re willing to do. In most cases, these tasks can be automated using macros written in Lua code, and often quite easily.

This tutorial is based on my own knowledge and experience. There are many features of the Lua language and the Aegisub API that I've never used and won't cover. This tutorial should provide a solid starting point, but advanced users will have to do their own research.

The next section will cover basic programming concepts for people who have never programmed before. If you already know a programming language (HTML doesn’t count), you can skip to "Lua for Experienced Programmers". If you already know Lua, or if you'd rather start writing macros right away and learn Lua as you go, then you can skip to "The Aegisub Environment".

I recommend frequently referencing the documentation provided in the Aegisub user manual.

Programming for Beginners

This section will briefly introduce basic programming concepts using Lua. On the advice of some people who helped proofread this tutorial, I've vastly condensed this section, as it is not the main focus. You can find many good resources for learning Lua on the lua-users.org page.

Variables, data types, arithmetic, and logic

A variable is the name of the place where you store data. They behave similarly to the variables in algebra, but can store any kind of data, not just numbers. x=5 y=12 z=x+y

Another simple data type in addition to numbers is the string, which represents text and is defined with quotes. In Lua, you can join one string to another using the .. operator. first_name="John" last_name="Smith" full_name=first_name.." "..last_name message="Hello world!"

Note that the backslash (\) is an "escape character" (more info here). If you want to type a normal backslash, type "\\".

Another data type is the Boolean, which can only have two values: true or false. You can use Booleans to evaluate if conditions or loops. is_happy=true if is_happy then print("I’m happy too!") end

In most cases, you will be using Boolean expressions that can be true or false depending on the situation. The following are examples of boolean expressions. x > y number <= 0 count == 5 command ~= "quit" x < 0 or x > 10 x > 0 and y > x The greater than and less than sign mean what you expect. "<=" and ">=" mean "less than or equal to" and "greater than or equal to", respectively. "~=" means "not equal to". Conditions can be combined using and and or. Note the double equals sign. In many programming languages, including Lua, the single equals sign does not mean "is equal to". Instead, the single equals sign represents assignment. That is, it is a command that means "store this value in this variable". The expression count = 5 means "store the value 5 in count", which is not what we want here. To check for equality, use count == 5, which means "count is equal to 5".

If you are working with number variables, you can perform arithmetic on them normally. In Lua, multiplication is *, division is /, and exponentiation is ^. The modulo operation is %. x=21 y=3*x+46 z=((y-x)/(y+x))^2 x=15+y Keeping in mind that the equals sign is the assignment operator, note that at runtime, the expressions on the right are evaluated and their numeric results are stored in the variables. Changing the value of x on line 4 does not affect y or z.

Lua has a special value called nil that can be stored in any variable. It means "nothing". This is not the same as the number 0, nor is it the same as a string with no text in it. If a variable is nil, it means "no valid data is stored in this variable". Uninitiated variables and failed function calls will generally result in nil.

You might think having nil values is an error that should be avoided, but nil values can be very useful. For example, you can see whether tonumber(foo) returns nil (i.e. "failed to covert to number") to determine whether foo is a valid number or not. The last thing I have to say about variables concerns naming them. A variable name can contain letters, numbers, and the underscore, but no spaces. The first character in a variable name must be a letter or the underscore. Also, you cannot use any Lua keywords as variable names. For example, you cannot name your variables end, if, for, or in, because all of these serve other purposes in Lua. Control structures and loops

Now we can start doing some more complicated tasks than arithmetic. I already briefly touched on the if statement. An if statement checks whether a condition is true, and if it is, it performs all of the code until end. Otherwise, it skips the code and continues after the end. x=5 y=10 z=0 if x

Here, because x is less than y, the code inside the if statement executes. z is set to 50, and "Foobar" is printed out to the console. Afterwards, "The value of z is 50" is printed to the console as well.

You can extend an if statement using elseif and else. if command=="quit" then print("Goodbye!") return elseif command=="say hi" then print("Hello, "..name.."!") else print("I didn't understand your command.") end

Next up are loops. Loops will execute the code inside them over and over until some condition is met. Two basic loops are while... do and repeat... until. These should be self-explanatory, so I'll just show some examples and move on. x=100 y=0 while x>0 do x=x-1 y=y+x end print("The value of y is "..y) repeat print("Say quit to exit this program") user_input=io.read() until user_input=="quit" print("Goodbye!")

One very important kind of loop, and one that many beginners find hard to understand, is the for loop. The for loop will cycle a variable or variables through a set of values, and execute the code in the loop once each time. The simplest use of a for loop is as a counter, to cycle a variable from a starting value to an ending value. For example, to count from 1 to 10, we simply do: for x=1,10,1 do print(x) end

This for statement says "define a variable named x, which will start at 1, end at 10, and count by 1." The third number is optional; if you put for x=1,10 do Lua will count by 1 by default. If you want to count by 2, then use for x=0,10,2 do, and if you want to count down, use a negative number. The commands inside the for statement (in this case, a print) will be executed for each value of x.

As a simple example, let's use for to calculate the factorial of 10. result=1 for num=1,10 do result=result*num end print("10! = "..result)

The variable num stores values from 1 to 10, and the code inside the loop is executed once for each of those numbers, thus multiplying result by all the numbers from 1 through 10.

Arrays and other data structures

So far, we've covered three simple data types: numbers, strings, and Booleans. Now we can move on to compound data types, the most basic of which is the array, which exists in all programming languages. An array is an ordered list of values. Because it's ordered, each value has an associated index: 1 for the first value in the list, 2 for the second value, and so on. You use this value to access specific elements in the array. my_array={8, 6, 7, 5, 3, 0, 9} print(my_array[3])

In the above code, my_array[3] refers to the third value in the array. 7 is at the third position in the list, so the output of this code is 7.

We can use a for loop to perform operations on every item in an array. By placing a # in front of the name of the array, we can get the size of the array (the number of items in the list). We can use this as the upper bound for our for loop. my_array={8, 6, 7, 5, 3, 0, 9} for i=1,#my_array do print("The number at index "..i.." is "..my_array[i]) end Since arrays are used so often in Lua (they're actually a part of a bigger data type, known as tables), a special function is provided that lets you cycle through an array more easily. This is the ipairs function. The i presumably stands for indexed, meaning this function gives the elements of an array in order based on their index. The "pairs" is because it returns index-value pairs. You use ipairs like this: my_array={"Never", "gonna", "give", "you", "up"} for index, value in ipairs(my_array) do print("The string at index "..index.." is "..value) end Any data type can be stored in an array: numbers, strings, and even other arrays. If you have an array of arrays, then you can, for example, access the third element in the second array using array[2][3]. Unlike many programming languages, Lua allows you to have different kinds of values stored in the same array. You can thus have a list of mixed numbers, strings, booleans, and tables.

The other main compound data type in Lua is the hash table (again, this is actually a part of the table data type; more on this later). Instead of storing an ordered list of values, a hash table can be thought of as a dictionary or a lookup table. A hash table stores pairs of "keys" and associated "values". You use the key to look up the corresponding value in the table. The key is usually a string.

The example below shows how to define a hash table to store data on a person.

person={ fname="John", lname="Smith", age=17, favfood="pizza" }

To access the person's first name, simply use person["fname"]. Lua also allows you to access the data in a way that looks more "object-oriented" (if you don't know what this is, don't worry about it). The code person.fname means the exact same thing as person["fname"].

Note that you cannot compare arrays and hash tables using == and other similar operators. Well, you can, but it probably won't do what you want it to. An important caveat! When you want to make a copy of a simple data type, you can simply create a new variable and set it equal to the old one. This doesn't work for compound data types like tables, because the variable is a reference to the table data, and not the data itself.

Aegisub comes with a function to copy tables. See the "Utils" section for more details.

Functions

Functions are similar to the functions you learn about in pre-algebra. You give the function some parameters, it does some work on the parameters, and it usually gives you a result. If you have a function like f(x)=4x+2 and you ask for the value of f(6), then 6 is your "parameter" or "argument" and 26 is your result or, in programming terms, your "return value". You can have a function with multiple parameters, such as g(x,y)=x+y-4.

The below code defines a function that acts like g(x,y) above. function add_and_minus_four(num1, num2) result=num1+num2-4 return result end

This function takes two parameters, does some math using them, and returns the result to us. You can use this function elsewhere in the program like this: x=12 y=16 z=add_and_minus_four(x,y) print(z)

The resulting value of the function, 24, will be stored in z. For reference, the above code can be written in one line as: print(add_and_minus_four(12,15))

A function does not need to have a return value, because unlike math functions, a function in a program can do tasks other than calculating a result. Functions also need not have any parameters, but when you use the function, you always need the parentheses, even if there is nothing in them.

Also, unlike math functions, a Lua function can return multiple values, separated by commas. To return multiple values, just write return value1, value2, ... at the end of your function. Then, when you use the function, store the multiple returns in variables separated by commas: x1, x2, ... = ....

It's important to note that a function immediately ends once it gets to a return statement. Any code after the return statement will not execute. Thus, if you have a return inside an if statement, there is no need to put an else. function greater_of_two(num1, num2) if num1 > num2 then return num1 end return num2 end

Lua for Experienced Programmers

This section will give a quick overview of what you need to know about Lua if you already have programming experience. If you're a beginner programmer who has just finished reading the previous section, I still recommend reading this section in full, especially the "Useful libraries" section. You might not recognize all of the terms, but there's a lot of essential information. I'll also repeat a lot of what I covered in the previous section, but with higher-level explanation.

Writing Lua code

First things first: what program should you use to write Lua code? Personally, I use Notepad++, which comes with syntax highlighting for Lua and many other languages. If you download Lua for Windows, it comes with an editor specifically designed for Lua. Otherwise, I'm sure you can just google "Lua editors" and find something to your liking.

Keep in mind that macros you write will only run in the Aegisub environment and can only be tested within Aegisub. However, you can test out code snippets outside of Aegisub so long as they don't use to the Aegisub API.

Basic syntax and features

Lua is a lightweight, weakly typed, interpreted (sort of) programming language designed as a scripting language (yes, this was paraphrased from Wikipedia). You can define variables on the fly without having to specify what data type they store, and the same variable can be used to store any kind of data.

There are only three simple data types (that I can think of): number, string, and Boolean. All numbers are double-precision floating point numbers.

Lua arrays start counting at 1, not 0, so be careful. The size operator for arrays and strings is the prefix #.

The end of a control structure or function's scope is marked by the end keyword. There is no begin keyword; the function definition or the head of the control structure serves to begin the scope. An exception is repeat... until, where the two keywords define the scope and no end is needed.

Lua supports the local keyword for variables and functions, if you need to control the scope. Generally you don't need to worry about this if there's no chance of confusion, but it's still good practice. Variables not declared local are global.

Uninitiated variables or array entries are set to nil. This is more or less equivalent to the null value in Java. Failed function calls often return nil. This can be used, for example, to detect if a string does not contain a certain substring: string.match(str, substr) == nil (incidentally, = is assignment and == is equality). Lua supports multiple return values and arbitrary numbers of function parameters. If you pass a function more parameters than it uses, the extra parameters are ignored. If you pass the function too few parameters, the remaining parameters are nil. A function can receive an arbitrary number of parameters using ... (look it up). Lua does not have a full regular expression implementation. Lua uses a bare-bones kind of regex known as "patterns". This tutorial and Google should teach you all you need to know. Patterns will come up again later when I introduce the string library. For strings, the escape character is the backslash (\). Remember to escape slashes when writing .ass override tags.

In Lua, functions are "first-class citizens". That means you can toss around a function as if it were a variable. Pass a function as a parameter, store a function in an array—go wild.

Boolean operators are and and or. "Not equals to" is ~=. String concatenation is ... You don't need semicolons at the ends of lines, and superfluous whitespace is ignored. Comments are defined as follows:

--This is a single line comment --[[ This is a multi-line comment ]] The table data structure

The table is the only native compound data structure in Lua. It can be used like an array, a hash table, or both. Multiple data types can be stored in the same table. Fundamentally, a table is a collection of key-value pairs. If the keys are all numbers, then the table behaves like an array. If the keys are all strings, then the table behaves like a hash table. If you have both at the same time, then welcome to the joys of Lua tables!

The iterator function pairs returns all the key-value pairs in a table, in both the array part and the hash table part. The pairs function does not guarantee any order to the values it returns, not even for the array part of the table. You use the function like this: for key, value in pairs(my_table) do ... end You might be familiar with "for each" loops in other programming languages; this is basically that. If you want to iterate through only the array part of the table, in order, then replace pairs with ipairs. Object-oriented Lua

As you may have realized, tables are pretty similar to objects. Indeed, Lua allows you to extend tables to use them like objects using metatables. I've never used them personally, but if you are interested, you can find more information online.

But insofar as a table can be a hash table, the keys are a lot like an object's fields. Defining a table can look a lot like defining an object; scroll up to see an example. Lua even allows you to write my_table["key1"] as my_table.key1. Useful libraries

In addition to the pairs and ipairs functions, Lua also provides the useful tonumber and tostring functions.

The table library is worth looking into, but the only functions from it that I regularly use are table.insert and table.remove. The string library

I could just link to the string library tutorial and the patterns tutorial on the Lua users wiki and call it a day, and honestly, you should find most of what you need on there. But I'll go through it in a bit more detail anyway.

First, it's worth noting that you can call string library functions on strings in an object-oriented-like way. For example, if you have a string named str and a pattern named pat that you want to match, you can use either string.match(str, pat) or str:match(pat). This is a bit shorter and somewhat more intuitive, especially if you're used to object-oriented.

The functions I use most frequently when writing Aegisub automations are string.match, string.format, string.gmatch, and string.gsub. I used to use string.find, but I found that in most cases, string.match is a better option.

These functions are, in general, pretty well explained on the Lua users wiki. Note that string.match returns the section of the string that matches the pattern, or the captures if the pattern contained any (these are returned as multiple return values). If you've used format strings in C before, string.format is basically the same thing. You can often get the same functionality just by concatenating strings (since numbers are automatically converted to string when concatenated), but it's often neater and more convenient to use string.format.

The real workhorse, though, is string.gsub. This is the bread and butter of most automation scripts that I've written, because most Aegisub automation involves modifying the text of your subtitle script. There's no better or more versatile way to modify text in Lua than string.gsub. Its many capabilities can be overwhelming for some, so I've written an example script that should walk you through what you can do with it.

You can download the example script here.

The math library

Here's the math library tutorial. There's not much I can add to this. The math functions you'll use depend heavily on the sort of automation you're writing, so it's best to look them up as you need them. However, I will mention a couple things.

First off, be aware that all trig functions in the math library use radians! I cannot stress this enough. As you're probably aware, advanced substation alpha angles are always in degrees, so if you want to do any math involving angles, it is imperative that you use math.rad and math.deg to convert from degrees to radians and vice versa. Many a time I have been stymied by misbehaving code, only to realize I'd forgotten to convert. Also note that the math library includes the constant math.pi.

Another thing to note is Lua's pseudorandom number generator. The seed is always the same, so if you run your automation multiple times, math.random will produce the exact same sequence of pseudorandom numbers. If you want to get a different pseudorandom number sequence, use math.randomseed to seed the random number generator with a different number. A good solution is to use your constantly-changing system time as a seed: math.randomseed(os.time()). This will produce a new sequence of numbers each time you run the automation... so long as you wait a second. Sadly, Lua doesn't do milliseconds.

The Aegisub Environment

Finally, it's time to see how we can put Lua to work in Aegisub's automation environment. All this information and more is on the official user manual on the Aegisub website, but I'll be presenting it here step-by-step, with explanations and examples. Nonetheless, I strongly encourage you to read the manual thoroughly on your own time.

Writing a macro function

The most basic skeleton of an automation script should look something like this:

--[[ README: Put some explanation about your macro at the top! People should know what it does and how to use it. ]] --Define some properties about your script script_name="Name of script" script_description="What it does" script_author="You" script_version="1.0" --To make sure you and your users know which version is newest --This is the main processing function that modifies the subtitles function macro_function(subtitle, selected, active) --Code your function here aegisub.set_undo_point(script_name) --Automatic in 3.0 and above, but do it anyway return selected --This will preserve your selection (explanation below) end --This optional function lets you prevent the user from running the macro on bad input function macro_validation(subtitle, selected, active) --Check if the user has selected valid lines --If so, return true. Otherwise, return false return true end --This is what puts your automation in Aegisub's automation list aegisub.register_macro(script_name,script_description,macro_function,macro_validation) To view this skeleton script with syntax highlighting, you can download it here and open it up in your Lua editor of choice.

When you run the automation from Aegisub, it will call macro_function (or whatever you choose to name your function) and execute the contents of the function. The function is given three parameters: the subtitle object, a list of selected lines, and the line number of the currently active line. Most scripts only use the first two. If that's the case, feel free to not include the active parameter. Also, to save typing, I usually abbreviate the parameter names to sub, sel, act. You can name the parameters whatever you want. Much like in math, f(x)=2x+3 is exactly the same as f(y)=2y+3.

For convenience, I will use my preferred variable names sub, sel, act to stand in for the three parameters in code examples. The subtitles object is the only object that contains data about your .ass script. It's a table, but it's a pretty special one. The array part of the table stores all the header, style, and dialogue lines in the script. To get line 5, you simply type sub[5]. Now this is important: if you want to modify lines, you never modify the subtitles object directly. Not only is it a pain to type, I don't think it actually works (like I said, the subtitles object is a special table). You have to first read out the line using something like line=sub[line_number]. Then you modify the line, and put it back in the subtitles object with sub[line_number]=line. Incidentally, these lines that you read out are line data tables, which I'll cover later. I'm going to be honest: to this day, the subtitles object is something of a black box to me. It's pretty complicated, but all you really need to know is how to retrieve dialogue lines. Aegisub comes with some very useful functions that do the rest of the work for you (I'll cover them in the section on karaskel).

Next up is the list of selected lines, which I usually call sel. To reiterate: only the subtitles object contains data about the script. You won't find the selected lines in sel. Instead, you'll find a list of the line numbers of the selected lines. In other words, sel is a list of indices for the subtitles object. To get the first selected line, you have to write sub[sel[1]]. This can be a bit confusing, especially if you use ipairs on sel, as I will show you how to do later. You'll end up with two indices: one that refers to a position in sel and one that refers to a position in sub. Don't get confused.

The final parameter is the line number of the currently active line. I've never used it, but you can access the relevant line using sub[act]. Use these parameters to do all sorts of fancy stuff to the subtitles in the body of the function. At the end, you should set an undo point. Aegisub 3.0 now automatically does this even if you forget, but it's good practice to do so anyway. The return value of the function is optional, but if you return an array of line numbers, then the selection will be set to those lines once the macro finishes running.

With this in mind, here's a skeleton for a processing function that will allow you to modify every line in the user's selection. function do_for_selected(sub, sel, act) --Keep in mind that si is the index in sel, while li is the line number in sub for si,li in ipairs(sel) do --Read in the line line = sub[li] --Do stuff to line here --Put the line back in the subtitles sub[li] = line end aegisub.set_undo_point(script_name) return sel end Download this skeleton here.

The dialogue line table

Read this. Just do it. Be aware that without using karaskel, you only have access to the "basic fields".

The dialogue line data table stores all the information you will need about a line in your script, all in a nice, convenient table. You can access the actor and effect fields, you can get the start and end time in milliseconds, you can tell whether the line has been commented out (and you can change a line to a comment or vice versa), and most importantly, you can access and modify the text of a line.

There are a plethora of useful macross that you can write using nothing but the skeletons I provided above, the string library, the math library, and line.text. If you just want to take care of some simple text modifications such as adding italics, or even some basic math like shifting position, then you have all you need to know. You can stop reading after the following two example scripts.

For those who want to get the most out of the Aegisub automation API, continue on to the advanced section.

Guided Example: Add Italics to Selected Lines

[Download example script]

This is one of the simplest examples I can provide. I've only added one line of code (line 25) to the skeleton scripts I've provided above.

In this script, I use the string concatenation operator to add an italics tag to the start of each selected line's text. Note that there are two backslashes. As mentioned in the variables section, the backslash is a special character. To type an actual backslash, we need to "escape" it.

When using the skeleton scripts, don't forget to fully delete pieces of code that you are not using, and make sure you reflect name changes across the entire script. Here I've named the processing function "italicize", so I also have to use "italicize" when I register the macro. Furthermore, I did not need a validation function since this script can be run on any selection of lines, so I deleted the validation function and removed it from the macro registration at the bottom.

Finally, remember to set your script properties at the top, and write a readme.

Guided Example: Modify Font Size for Mocha

[Download example script]

Nowadays xy-vsfilter, which supports non-whole-number scaling, is becoming standard, so this trick is no longer quite as useful. Nonetheless, it makes for a good example automation script that involves both the string library and the math library.

This script decreases the font size (\fs) by a factor of ten and increases the scaling (\fscx and \fscy) by a factor of ten. At the end, the size of the text still looks the same, but the scaling is ten times more precise. For font sizes that are not evenly divisible by ten, the script will round down to the nearest whole number. Then it divides the original font size by the new font size to determine how much it needs to increase \fscx and \fscy to balance it out. All this math takes place in lines 40 to 48 of the example script, so you can see it for yourself. math.floor is the function that rounds down to the nearest whole number (the opposite is math.ceil).

The script makes use of string.gsub with an anonymous function. If you're having trouble with understanding this use of string.gsub, you can check out the example script I wrote, or the string library tutorial on the Lua users wiki. The pattern "\\fs(%d+)" looks for the string "\fs" followed by a number, and the parentheses around %d+ will "capture" the number and send it to the anonymous function (another reminder that you have to escape backslashes).

The script also shows how to use string.format to insert values into a format string. The formatted string is then returned by the function, and substituted into the original string.

Unlike the previous script, this one has a validation function that makes sure the selected lines all contain "\fs". Note that I do not read out a line table, but directly access the subtitles object. Since I'm not going to be modifying the lines, I'm not going to bother reading the line into a full line data table. Validation functions run every time you open the automations menu, so they should be as short and as fast as possible.

The Aegisub Environment - Advanced

Before you go off writing your own functions to do useful subroutines, make sure that you're not reinventing the wheel. Aegisub comes with two libraries that will vastly extend the capabilities of your automation scripts and make common tasks much easier. Later in this section, I'll also introduce the API for creating simple GUIs that allow the user to configure the behavior of the automation script. karaskel.lua

The full documentation for this library can be found here. To use karaskel functions in your script, put this at the top: include("karaskel.lua") Remember all the extra fields that you might have seen when reading about the dialogue line data tables? Well, karaskel will give you access to all of that. You'll also be able to access style data about a line, so you can detect its style defaults, which is huge if you're writing an advanced script.

Since my scripts are typesetting-focused, I only use two functions from karaskel. If you want to write an automation that actually deals with karaoke, then you'll probably find the numerous other features of karaskel quite useful.

The first essential function is karaskel.collect_head, which collects the meta and style data that other karaskel functions use. You'll need a line at the top of your processing function that looks something like this: local meta, styles = karaskel.collect_head(sub,false)

You probably want false, because true will generate a lot of mysterious extra styles in your script. That's not where the magic happens, though. After you've read in your line to process it, you can do this: karaskel.preproc_line(sub,meta,styles,line)

This gives you access to all the extra dialogue line fields that you saw earlier. In particular, it gives you access to line.styleref, which is exciting. Seriously. Be excited.

Because now you can do things like line.styleref.color1 to figure out what the default main color of your line is. You can check the default font size using line.styleref.fontsize. Need to know the border weight? line.styleref.outline is your friend.

Be warned that the color codes extracted from the style are not ready to use in \c tags just yet. Color codes in style definitions contain both color and alpha data, and look a bit different from in-line override codes. You'll need a function from utils.lua to extract properly formatted color and alpha codes for use in override tags.

As a side note: a function that isn't part of karaskel but can be very useful is the aegisub.text_extents function, found in the miscellaneous APIs. You'll need to use karaskel.collect_head before you can use this function, which is why I mention it here. This function takes a style table and a bit of text and calculates the pixel height and width of the text. If you pass it line.styleref, then it will give you the size of the text in the line's default style. But you can do more. If the user overrides the default font properties in-line, you can use the strings library to detect the changes. Now make a copy of the line's style table, modify it until it matches the user's override tags, and pass it to aegisub.text_extents. You can determine the size of any typeset that the user makes, even if he changes the font or font size in the line. That opens up a lot of possibilities. utils.lua

The full documentation for this library can be found here. To use utils functions in your script, put this at the top: include("utils.lua")

This library defines the essential table.copy function. If you want to create new lines in your subtitle script, you're going to need this function. As mentioned in an earlier section, copying a table is more involved than copying a number or a string. To create a new line, you're going to have to make a proper copy of your original line data table, and you'll need this function to do that.

In addition, utils contains lots of useful functions for working with .ass color and alpha codes. There are functions to extract colors and alphas from styles, to convert to and from RGB values and HSV values, to transition smoothly between colors and alphas, and so on. If you're stuck working with colors or alpha, odds are one of the functions here will help you out.

Creating GUIs

For those who don't already know, GUI stands for Graphical User Interface. Anything that has a pretty window with buttons you can click is a GUI, and we can create simple GUIs to allow users to set options for your automation before it is run.

The GUI documentation is here, and if you're anything like me, you're going to want an example so you can see how this thing works, because it's a little hard to grasp just reading the documentation.

Let's work backwards. The function that actually displays your dialog box is aegisub.dialog.display. This function takes two parameters, both of which are tables. The second table is easy enough. It's just a list of the buttons you want at the bottom of the table. Something like {"Run", "Options", "Cancel"} would work. The function's first return value is the button that was pressed.

The first parameter is the real meat of the GUI. This is the dialog definition table, which will describe how your GUI is laid out, what options the user has, and what their default values are. You can see all the options available to you here.

You position components using a grid of rows and columns. Imagine each component is a cell in a spreadsheet. The top left corner is (0,0). To the right of it is (1,0), and below is (0,1). If you want a component to occupy more than one cell, then set its coordinates to the top left cell, and use "width" and "height" to tell it how many columns and rows it takes up. You can make your GUI as many columns wide and as many rows tall as you want, and the window will stretch to fit—but keep it within reason.

The type of component is defined by the "class" property. You can have labels, which simply provide instruction text, you can have checkboxes, dropdowns, color selectors, text fields, and so forth. Components that take user input also need a "name" so that your automation can retrieve the results later on. Additional properties will depend on the class of the component. Checkboxes can be set to "true" or "false" by default, and can also have their own labels. Dropdowns will contain a list of the options to include in the dropdown menu, and so forth.

All right, enough of that. You want to see an example. So here we go.

Let's say we want to add some options to our italics script. We want a dropdown box to let the user select "Apply to selected lines" or "Apply to all lines". This will be set to "Apply to selected lines" by default. Also, perhaps the user wants to unitalicize lines that are italic already. So we should have a checkbox for "Unitalicize already italic lines", but this will be off by default. Also, maybe the normal italic isn't slanted enough, so we can provide a text box to let the user define a \fax value, to tilt the text more (humor me here). We'll need to label this text box so the user knows what it does, and let's set its default value to zero.

Here's what our dialog configuration table looks like: dialog_config= { { class="dropdown",name="lineselect", x=0,y=0,width=1,height=1, items={"Apply to selected lines","Apply to all lines"}, value="Apply to selected lines" }, { class="checkbox",name="unitalic", x=1,y=0,width=1,height=1, label="Unitalicize already italic lines", value=false }, { class="label", x=0,y=1,width=1,height=1, label="\\fax value:" }, { class="floatedit",name="faxvalue", x=1,y=1,width=1,height=1, value=0 } }

When we call aegisub.dialog.display(dialog_config), we see this:

Well okay, the arrangement could use a bit of work. Let's see if we can't make this look better by modifying the x, y, and width properties. Let's make the dropdown and the checkbox two columns wide, and we'll move the checkbox to (0,1), below the dropdown. The label and the float edit box will still be one column wide, and we'll move them down a row.

There! Much nicer. We've created our first GUI. If you want to change the default OK and Cancel buttons, just add a second parameter containing a list of desired buttons and you'll be set.

Now that we know how to display GUIs, we need to know how to use the results of the user input. These results are stored as a hash table and are the second return value of aegisub.dialog.display. The keys in this hash table are the names of the components that we defined in the dialog configuration table. If, in our example, we store our results in a table named results, then to access the selected option in the dropdown box we use results["lineselect"]. To see whether the checkbox was checked, we'll see if results["unitalic"] is true or false. To get the value we should use in the "\fax" tag, simply take a look at the number in results["faxvalue"]. So, to summarize: buttons={"Italicize","Cancel"} dialog_config= --See above pressed, results = aegisub.dialog.display(dialog_config,buttons) if pressed=="Cancel" then aegisub.cancel() end --Handle the results table...

Miscellaneous

I'll add to this section as I think of miscellaneous things worth mentioning. If you've followed the tutorial so far, you should be set for the most part.

The automation progress bar can be controlled using functions found on this page. The functions are entirely self-explanatory. It's mostly aesthetic, but can also help in debugging by giving you a rough idea of where in the processing your script encounters an error. Speaking of debugging, further down on the same page are a few debug output functions.

The miscellaneous APIs page I mentioned earlier also has some great functions for getting information about the video. Furthermore, there's the aegisub.decode_path function that's very useful if you want to save and load files. Aegisub defines several helpful path specifiers that let you access directories such as the application data folder or the location of the video file.

Lua patterns are powerful enough for the most part, but still limited. Aegisub's documentation includes the re module which is supposed to allow for more robust regular expressions. I tried to use it once and ended up ragequitting. Perhaps I was doing something basic wrong and the module will give you no problems, but consider yourself warned. I, for one, will stick to patterns.

My point of view when writing this tutorial was mainly that of a typesetter, but if you're a kfxer, you'll find tons more good stuff in the karaskel library that I didn't even mention here. Knowing all the capabilities of Aegisub's Lua environment more in depth will help you pull off more advanced karaoke. Guided Example: Modify Font Size Revisited

[Download example script]

Remember the long list of exceptions in the "simple" version of this macro? It was only really useful in a specific set of cases, and relied heavily on the typesetter not doing anything that the macro did not expect.

I really hate writing macros like this, and your users will be frustrated too. This is a bit of a tangent, but truly useful automations should be robust. They should behave as the user expects them to behave in the vast majority of cases. The previous version of this script couldn't handle relatively common situations like having a \fscx or \fscy tag in the line. Well, we can fix that.

Off the top of my head, the only thing this version of the macro doesn't handle is \t transforms (and if you're going to motion-track it, you shouldn't need to use \t). The comments do most of the explaining, but I'll still walk through the script here.

First up, we see our first use of karaskel, using the two functions explained in the karaskel section. Here karaskel is necessary to allow us to access style defaults. After that come some basic string manipulations to set up the text of our line for later. It's important that the line start with an override block, and that the first override block contain an \fs tag.

After that comes the first somewhat tricky part. I parse the line's text into a table, separating text from override blocks, structured in such a way that I can easily see what part of the line each override block affects. I can easily manipulate this table and use it to reconstruct the line at the end.

I don't have a proper name for this data structure, but let's call it a tag-text table. Here I've named the variable "tt_table". If you're having trouble telling how tt_table parses the line, I've drawn up a diagram. If our original line is:

{\fscx120\fs40}Never {\c&H0000FF&}gonna {\fs80}give {\fscy50}you {\fs69\fscx40\fscy115}up Then once we've parsed it, our table looks something like this:

tt_table

1 2 3 4 5 tag text tag text tag text tag text tag text

{\fscx120\fs40} Never {\c&H0000FF&} gonna {\fs80} give {\fscy50} you {\fs69\fscx40\fscy115} up

This means tt_table[3].text is "give", while tt_table[2].tag is "{\c&H0000FF&}". Plus, since an override tag affects everything to the right of it until it gets overridden again, we know that the contents of tt_table[2].tag are going to affect all the text stored in tt_table[3] through tt_table[5]. In other words, we can start at the left side of the table and move to the right, and at any point in the table we'll know exactly how the text will be rendered, based on all the override tags we've seen so far.

This data structure is the key to several of my most powerful macros, including fbf-transform and gradient-everything, and is what makes them so robust. With this, someone writing a macro can tell what the typesetter is doing at any point in the line.

It is worth noting that this relies on there being an override block at the beginning of the line. It's easy to check if an override block exists at the beginning, and simply append an empty one ("{}") if it doesn't.

Now, we make use of this data structure to help us properly refactor the font size. First, we'll store the state before the start of the line using style defaults. Let's say the default font size is 20, while the default x and y scales are 100.

tt_table

1 2 3 4 5

tag text tag text tag text tag text tag text {\fscx120\fs40} Never {\c&H0000FF&} gonna {\fs80} give {\fscy50} you {\fs69\fscx40\fscy115} up

↑ cur_fs=20 cur_fscx=100 cur_fscy=100

Then we enter the for loop and begin looking at the first override tag. We'll detect the \fscx and \fs values defined in tt_table[1].tag, and use them to update our state variables. Since there is no \fscy tag, cur_fscy remains unchanged.

tt_table 1 2 3 4 5 tag text tag text tag text tag text tag text

{\fscx120\fs40} Never {\c&H0000FF&} gonna {\fs80} give {\fscy50} you {\fs69\fscx40\fscy115} up

↑ cur_fs=40 cur_fscx=120 cur_fscy=100

Using these values, we can calculate that the new font size should be 4. In the previous version of the macro, \fscx and \fscy were simply set to 100 times the scale factor. This time, we'll use the scale values parsed from the line, so \fscx and \fscy values will become 1200 and 1000, respectively. The macro then removes the old tags, adds the new ones, and moves on to the next element in the table.

tt_table

1 2 3 4 5 tag text tag text tag text tag text tag text

{\fs40\fscx1200\fscy1000} Never {\c&H0000FF&} gonna {\fs80} give {\fscy50} you {\fs69\fscx40\fscy115} up

↑ cur_fs=40 cur_fscx=120 cur_fscy=100

This time, there are no font size or scale tags in this override block. The text here inherets the font size and scale changes we added to the previous tag block, so there's no need to add any more tags. We move on to tt_table[3].

tt_table 1 2 3 4 5 tag text tag text tag text tag text tag text

{\fs40\fscx1200\fscy1000} Never {\c&H0000FF&} gonna {\fs80} give {\fscy50} you {\fs69\fscx40\fscy115} up

↑ cur_fs=80 cur_fscx=120 cur_fscy=100

The macro detects the font size change, adds the relevant tags, and moves on.

tt_table 1 2 3 4 5 tag text tag text tag text tag text tag text

{\fs40\fscx1200\fscy1000} Never {\c&H0000FF&} gonna {\fs8\fscx1200\fscy1000} give {\fscy50} you {\fs69\fscx40\fscy115} up

↑ cur_fs=80 cur_fscx=120 cur_fscy=50

tt_table

1 2 3 4 5 tag text tag text tag text tag text tag text

{\fs40\fscx1200\fscy1000} Never {\c&H0000FF&} gonna {\fs8\fscx1200\fscy1000} give {\fscy500} you {\fs69\fscx40\fscy115} up

↑ cur_fs=69 cur_fscx=40 cur_fscy=115

tt_table

1 2 3 4 5 tag text tag text tag text tag text tag text

{\fs40\fscx1200\fscy1000} Never {\c&H0000FF&} gonna {\fs8\fscx1200\fscy1000} give {\fscy500} you {\fs6\fscx460\fscy1322} up

And we're left with our final converted line:

{\fs40\fscx1200\fscy1000}Never {\c&H0000FF&}gonna {\fs8\fscx1200\fscy1000}give {\fscy500}you {\fs6\fscx460\fscy1322}up Our macro handled all the crazy font size and scale variations in this line like a boss.

That being said, there's room for improvement in this example. Note that several redundant scale tags were inserted, when our script should be capable of detecting which scale tags are necessary to insert and which are not. I leave it as an exercise for the reader to come up with a way to fix this (hint: handle \fscx and \fscy the same way \fs is handled, and guarantee that \fscx and \fscy appear in the first override block).

I've shared more or less everything important about making automations that I know. Any other Lua libraries or techniques you might need will have to be researched on a case-by-case basis. Hopefully you found this tutorial useful in automating your own tasks. Happy coding! More Lua Scripting

I keep hearing people say "I need to learn some Lua" or "I need to learn to write automation scripts", and not many seem to really have gotten into it. This should help get you started. You should read lyger's guide first, because I'm not gonna explain the same things again, but I want to provide some more practical tips. Rather than explaining lua itself, I'll explain more about scripts for Aegisub specifically.

Learning Lua is not much of an issue. You can learn all the Lua stuff you need in an hour. It's mostly just if/then/end, the for cycle, gsub, and a few other things.

A large part of what you need is regular expressions, or rather Lua's simplified pattern matching. That, again, is something you can learn in an hour.

What I want to talk about the most is how to work with the Subtitles object, which is not really a matter of Lua, but rather of Aegisub and the ASS format. This is explained in the Aegisub manual, but since that may be confusing for beginners, I'll provide some specific practical examples. The goal is to explain how to write a basic automation script in as simple terms as possible. Once you understand how a script that adds blur works, adding more complex functions will be easy because that's just maths.

Here's the very basics: script_name="test" script_description="testing stuff" script_author="some guy" script_version="1" function test(subs, sel, act) -- stuff goes here end aegisub.register_macro(script_name, script_description, test)

The last line puts an entry in your automation menu. It has 3 parts. 2 of them are defined at the beginning - script_name and script_description. The name will appear in the menu and as an undo entry when you run the script. You can see description in the Automation Manager. The 3rd part means that running this script will run a function called "test". script_author and script_version aren't really important, but I'm sure you get the idea.

Let's look at function test(subs, sel, act) . I probably wrote at least 20 scripts before I actually understood what this is. Since this function is referenced by register_macro, it's the main function of the script, and as such is by default given the Subtitles object to work with. The 3 parts — subtitles, selected lines, and active line — give you 3 things you can work with.

You can name them whatever you want. You just have to stick to the naming. I tend to keep everything short, though I'm sure I'm not the only one who uses subs/sel/act. It's probably best to use these even just because of the fact that others do it too, which makes it easier to make sense of each others' scripts. subs is the whole subtitles object. You always have to use this. In simple terms, it's like a table of all lines in the ASS script, including headers, styles, etc. sel is selected lines, and if you want your function applied to all lines, you don't have to use this. You can have function test(subs). act is the active line, and you probably won't need it very often. You can use it for functions that are supposed to run on only one line or read some info from the active line. If you select one line and use sel, it's pretty much the same as using act.

The "stuff goes here" part is where the actual function will be written.

Here's an example of a simple function that runs on the whole script: function test(subs) for i=1,#subs do if subs[i].class=="dialogue" then line=subs[i] text=subs[i].text

line.effect="test"

line.text=text subs[i]=line end end aegisub.set_undo_point(script_name) end

The green part is what you'll usually have for every script that runs on all lines. The purple part is the actual specific function.

#subs is how many lines there are in subs (including headers and all). If the ASS file has 200 lines, the for cycle will run 200 times. You only want to apply this to dialogue lines, not to styles or headers, so you have to specify this condition: if subs[i].class=="dialogue".

So, the iterator i is going from 1 to 200, so when it's let's say 25, subs[i] is subs[25], or the 25th line in the ASS file. line=subs[i] means that you create element line and put subs[i] into it. Note that single = does not mean "equals". You could read it as "line is now subs[25]" (when i is 25). Then you work with line, and for it to be of any use, you have to put the line back in subs[i] at the end. line is something you created, subs[i] is the actual line in the subtitles, so you need the subs[i]=line at the end.

You see the same with text, even though in this case I don't need it, but usually you work with text the most. The purpose is to use something that's short instead of typing subs[i].text all the time. Also, it could also say text=line.text since line is already defined at that point. You can name those things anything you want, for example just l and t, which may be good for a short script, but again, line and text are commonly used by most of us, so it keeps things clear. aegisub.set_undo_point(script_name) sets the undo point, and should be at the end of the main function, though I think Aegisub does it automatically anyway. You can, however, create multiple undo points, like for every function in your script, but it's usually only confusing and not very practical.

Now, the actual thing this script does is line.effect="test". line.effect is the effect filed, and here it takes the value "test", which means the text of the effect field will be "test". So what this script does is that it puts "test" in the effect field of every dialogue line.

Now, the thing I did here with text would have made more sense if I'd done it with "effect" instead (because I didn't actually do anything with text), ie. effect=line.effect. Then the purple line could be just effect="test". You have to always think about what pays off and what doesn't. For this script, the purple line would be 5 characters shorter, but you would need two extra lines, to assign value to effect and to give the value back to line.effect, so that doesn't pay off. If you use something only once, you might as well keep it as is. The more often you use something, the more sense it makes to assign it to something with a short name.

Let's look at working with selected lines now. function test(subs, sel) for x, i in ipairs(sel) do line=subs[i] text=line.text

if text:match("you're tie is crooked") then line.effect="the editor is an idiot" end

line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end

I'm too lazy to do high-effort html, but you can paste the code into Notepad++ for proper syntax highlighting. You can see the for loop is using ipairs. sel consists of pairs of two numbers. The first is the index of the selection, and the second is the index in subs. If the ASS file has 50 lines and you select the last 3, then the x in ipairs will be 1, 2, and 3, and i will be 48, 49, and 50. In the previous example, x and i are the same thing because it goes through all lines.

Don't forget that the function must have (subs, sel). Of course you can always include the sel even if you're not using it, just to be sure that you never forget it. I pretty much always use (subs, sel) and in rare cases add act. The purple line is a basic example of an action dependent on a condition. You can read it as: If you find "you're tie is crooked" in text, put "the editor is an idiot" in effect. return sel makes sure that you keep the selection you started with (or new selection if you changed it).

You could also use for i=1,#sel do instead of ipairs, like we did with subs. If your script is deleting or adding lines, you need to go backwards, because the new/deleted lines are changing the index of the selected lines. If you delete line 1, line 2 becomes line 1 and line 3 becomes line 2, so going in the normal direction you'd either be skipping lines or going through them twice.

for i=#sel,1,-1 do local line=subs[sel[i]]

...

subs[sel[i]]=line

This is what I use. It starts at the last selected line and goes backwards to the first. i=a,b,c means it goes from a to b by steps of c. i=8,2,-2 would go through lines 8, 6, 4, 2. The default for steps is 1, so unless you go backwards like here, you don't need to write it.

An important thing is that if you use this, then line is subs[sel[i]], not subs[i]. here i is the number of the selected line, starting from 1, so if you used subs[i] when i is 1, you'd have the first line in the ASS file, not the first selected line. sel[3] is the number in subs corresponding to the 3rd selected line.

This thing kept confusing me for quite a while, so let's try a more specific example. Let's say subs has 50 lines (including headers and styles) and you select last 5 lines. sel would now be {46,47,48,49,50}. sel[1]==46 sel[2]==47 sel[5]==50 Using the for cycle will go from 1 to 5, so i will be 1-5, and sel[i] will be 46-50. subs[i] would be lines 1-5, which is not what you want. subs[sel[i]] will be lines 46-50. That's what you need.

So, that about covers the structure of the main function. With this and a bunch of if/then/end lines you can make simple scripts.

Now, let's look at some ways to manipulate text. text=text.." end of line"

This attaches a string to the end of text. text="Start of line "..text

This attaches a string to the beginning of text. This way you can add tags: text="{\\blur0.6}"..text

This is how the old "Add edgeblur" script worked. Of course, this doesn't join it with other tags, doesn't replace existing blur, etc. text="This is a test." text=""

Here the first one sets "This is a test." as text, deleting whatever was there before. The second one just deletes text, by making it an empty string.

gsub gsub is pretty much the core of every automation script. It's what replaces one thing with another. It works like this: text2=text:gsub("string A","string B")

This translates to: If you find "string A" in text, replace it with "string B" and assign the modified text to text2. I used text2 for the sake of explanation, but normally you'd use text=text:gsub, which just keeps the result in text.

"I could not see him." text=text:gsub("could not","couldn't")

» "I couldn't see him."

This way you can, for example, write a script for making contractions. text=text :gsub("([cws]h?)ould not","%1ouldn't") :gsub("did not","didn't") :gsub("was not","wasn't")

You only need the text=text part once. Then you can add as many :gsub lines as you want and create a whole list of contractions. While you can just add them one by one, you can also use pattern matching (lua's version of regexp) to keep the code short. The first gsub line will match could, would, and should. It will also match chould and whould, but as those don't exist, that doesn't bother us. The part in parentheses is a capture. [cws] means "c or w or s", and h? means "h if it's there or nothing if it's not". In standard regexp you could replace this capture with (c|w|s|ch|wh|sh) to get the same result. Lua doesn't have this option, so sometimes you just need more lines than you'd need with full regexp. The %1 is the capture, so whatever it matched in the first part will be pasted in the second.

Now we can use this to replace existing blur value with our new value. text=text:gsub("\\blur[%d%.]+","\\blur0.6")

Blur can have numbers and a decimal point, so use [%d%.]+ to match anything that's a number or a dot as many times in a row as possible, so whatever value the blur has will be replaced with 0.6. The same effect could be achieved in different ways: text=text:gsub("(\\blur)[%d%.]+","%10.6") text=text:gsub("\\blur[^\\}]+","\\blur0.6")

The first one captures the \\blur part, so you don't have to type it again (may be useful if it's something longer). The second one matches anything that's not a backslash or } as many times it can, ie. until it hits something that IS a backslash or }, which is where the blur value would logically end. This can pretty efficiently capture the whole value of any tag, since any tag has to end with \ or }. Of course with tags like \pos, you'll want to capture the coordinates rather than include the ().

You can also use a function within gsub: text=text:gsub("(\\blur)([%d%.]+)",function(a,b) return a .. 2*b end) a and b are the captures. The function uses them, returning a (\\blur) as is, and multiplying b by 2, thus giving you the blur value doubled. So you can divide your pattern into a bunch of captures and do some operations with them.

Here's how you capitalize the first letter of a line: text=text:gsub("^(%l)([^{]-)", function (c,d) return c:upper()..d end)

First capture is a lowercase letter at the beginning of a line. Second capture is from after the first letter until {, meaning before it hits a comment or tag. Returned is the first capture capitalized and second capture as is (which means it doesn't even have to be there in this case, but you could for example return d:lower() to be sure that the rest of the string will be lowercase).

Now you can understand how my Teleporter works: text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return "\\pos(".. a+xx.. "," ..b+yy..")" end)

Notice that literal ( and ), as in not captures, have to be escaped with %. Coordinates captures are [%d%.%-]+. You see that compared to what we had for blur, thse include %-, because coordinates can be negative. If you don't include that, the script will only work when coordinates are positive. So it captures X and Y coordinate, and adds to them the user input, which is xx and yy here. Yep, that simple.

One more example. This is "reverse move": text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)","\\move(%3,%4,%1,%2")

That's the whole thing. Capture the 4 coordinates and return them in changed order: 3, 4, 1, 2. This is a good example of how captures can be useful. You may notice that the ( is not escaped in the right half. Things in the right part of gsub don't need to be escaped with % - it's only used for captures. Only the left part uses regexp.

Escape characters

When using regexp, these characters have to be escaped with %: . ? * - + ( ) [ ] and % itself

Characters that have to be escaped with \: " ' and \ itself

Backslashes and quotation marks always have to be escaped, even in literal strings. (An actual quotation mark ends the string.) If you want to match an actual question mark in a sentence, you must match %?.

Regular Expressions

I'm not gonna explain regexp from scratch, because there's plenty about that on the Internet. What I'm gonna do is list some patterns that are useful for Aegisub automation scripts.

{[^\\}]-} -- comment (stuff between { and } that doesn't have a backslash) {\\[^}]-} -- tags (stuff between { and } that starts with a backslash) {[^}]-} -- comment or tags (stuff between { and })

The third one shows you a typical way of matching stuff between two markers. You match the first marker, then what's-not- the-second-marker with a - or *, and then the second marker. The difference between - and * is that {.-} matches only one comment or set of tags, while {.*}, if you have a string like "abc{def}ghi{jkl}" will match from the first { to the last }, so "{def}ghi{jkl}". You always have to think about whether you need +, -, or *. If you choose the wrong one, it may still work in simple cases, like if there's only one comment in the line, but it will break on more complex lines. I recommend creating a testing ASS file and fill it with all kinds of different lines, including with mistakes, bad tags, broken comments, etc. Have all combinations of text, tags, and comments, use some transforms, some mocha lines, anything that can be in a script. If you write a function, it needs to do what it's supposed to do no matter what line you apply it to.

%d+ -- sequence of numbers [%d%.]+ -- sequence of numbers, can have decimal point (values of \bord, \blur, \fscx, and so on) [%d%.%-]+ -- sequence of numbers, can have decimal point, can be negative (\xshad, \fsp, \frz...) &H%x+& -- values for colours and alpha %([^%)]-%) -- stuff between ( and )

%(([%d%.%-]+),([%d%.%-]+)%)

This will capture coordinates of \pos or \org. It could also capture fade in and out in \fad, though that doesn't need the -. For \move, capture 4 coordinates and don't include the ending %), because \move may or may not have timecodes. [%a']+ -- word (sequence of letters or apostrophes, to match words like "don't") [^%s]+ -- word (sequence of what's not a space)

You may need different ways of matching a word. The first one here will not include punctuation, the second one will. Sometimes you may need one, sometimes the other. You may also wanna replace %a with %w, if you want to include "words" like AK47 or just count 20 in "I'm 20 years old." as a word.

\\[1234]?c& -- colour tag (doesn't match value, just matches that the tag is there)

This matches \c, \1c, \2c, \3c, \4c, but not \clip (important!). (Also note that \\fs matches \\fsp and \\fscx, so be careful about patterns that may match things you don't want.) Since primary can be \c or \1c, in order to avoid complicated code that would deal with both, I recommend using this at the beginning: text=text:gsub("\\1c&","\\c&")

\c is what the inbuilt Aegisub tool creates, so keep those as standard.

Speaking off... tricks like this are often very useful. If your code needs to account for a lot of different things, see if you can reduce the number of these things with some easy trick. A common issue is for example matching the beginning of a word. A word starts either after a space, or at the beginning of a line. You need to match two patterns for that. However, you can start with adding a space at the beginning of a line, then use just one matching pattern, and then remove the space at the end of the script.

Another thing is dealing with lines with and without tags (when working with text). You can start with this: tags="" if text:match("^{\\[^}]*}") then tags=text:match("^({\\[^}]*})") end text=text:gsub("^{\\[^}]*}","")

If the line has no tags, then tags will be an empty string. If it finds tags at the beginning, they will be saved to tags, thus replacing the empty string. Then the gsub deletes the tags. Now you can work with the text knowing that you have no tags in the way. When you're done with the text, you'll do this: text=tags..text

This attaches your saved tags at the start of the line. If there were no tags, you have an empty string saved there, so basically nothing happens.

Another trick I use is when I want to add some tags, and the line may or may not already have some tags. text="{\\tag}"..text -- add tag when no tags are present text=text:gsub("^{\\","{\\tag\\") -- attach tag before other tags text=text:gsub("^({\\[^}]-)}","%1\\tag}") -- attach tag after other tags

These would be the regular options. The second and third depend on what you want to do. Just a matter of preference. It works either way. But you have to first find out if the line has tags, and then use the appropriate method. So again, there's a way to avoid that. if not text:match("^{\\") then text="{\\}"..text end

You start with adding {\} at the beginning of a line without tags. The second and third method of adding tag now work just fine, but you have an extra backslash somewhere in there. It will end up as either a doubleslash somewhere in there, or at the end before }. So at the end of the script, you do a simple cleanup.

:gsub("\\\\","\\") :gsub("\\}","}")

:gsub("{}","")

The first two are what you really need. The third is another "cleanup" line, useful when you've removed some tags and possibly ended up with just empty {}. (Of course the gsub is for text.)

\\t%([^%(%)]-%) -- transforms \\t%([^%(%)]-%([^%)]-%)[^%)]-%) -- transforms with \clip in them

The tricky thing about transforms is that they can have () within () if there's a transform for a clip, so to efficiently get all transforms, you always need both patterns. Yeah, this is a bit messy. Matching transforms is useful when you modify tags' values but don't want to change the tags inside transforms. You create transforms="". Then you match those two patterns and save them for example to tf1 and tf2. Then do transforms=transforms..tf1..tf2 and you'll have transforms saved in the transforms string. Then you remove them from the text with gsub and work with the text... and at the end put them back. This is a bit complex and you actually need gmatch because there may be many trnasforms. So once you get familiar enough with the code and everything, here's what I do: function trem(tags) trnsfrm="" for t in tags:gmatch("(\\t%([^%(%)]-%))") do trnsfrm=trnsfrm..t end for t in tags:gmatch("(\\t%([^%(%)]-%([^%)]-%)[^%)]-%))") do trnsfrm=trnsfrm..t end tags=tags:gsub("(\\t%([^%(%)]+%))","") tags=tags:gsub("(\\t%([^%(%)]-%([^%)]-%)[^%)]-%))","") return tags end

You run this function on tags. It goes through every instance of a transform and adds it to the trnsfrm string. When you're done with whatever you're doing to the other tags, you put this string at the end of the tags.

Some more regexp examples:

\\i?clip -- match clip or iclip \\[xy]?bord -- match bord or xbord or ybord

-- remove spaces at the beginning and end of a line text=text:gsub("^%s+","") :gsub("%s+$","")

GUI

Here's a very simple GUI: dialog_config= { {x=0,y=0,width=1,height=1,class="label",label="\\blur",}, {x=1,y=0,width=1,height=1,class="floatedit",name="blur",value=0.6}, } buttons={"blur","cancel"} pressed,res=aegisub.dialog.display(dialog_config,buttons) if pressed=="cancel" then aegisub.cancel() end if pressed=="blur" then blur(subs, sel) end dialog_config is a table with all the stuff in the GUI except the buttons. This one contains two things - a label, and an editbox for numbers. The label is "\\blur". That's what you'll see in the GUI, followed by the editbox, which will have "0.6" in it as default value. buttons is the buttons you will click on. aegisub.dialog.display is what displays the GUI, using the dialog_config and buttons. pressed determines which button was pressed. (You can name this whatever you want.) res is the user input from editboxes, checkboxes, etc. I started with "pressed, result", as that's what lyger's guide had, but over time changed "result" to "res" because it gets typed a lot. Again, it can be anything you want. As you can see in the last line, if you press the "blur" button, function blur(subs, sel) will be executed. To get the blur value inside that function, you'll use this: blurtag="\\blur"..res.blur res.blur is the value given by the user, so if you type "1.5" in the editbox, blurtag will now be "\\blur1.5".

Other types of input:

-- Checkbox {x=0,y=1,width=1,height=1,class="checkbox",name="nobreak",label="remove linebreaks",value=false},

Usage: if res.nobreak==true then (stuff) You don't have to type "==true" because that's implied by default, so it can be just: if res.nobreak then followed by what should be done if the checkbox is checked.

The opposite would be either if res.nobreak==false then or if not res.nobreak then. -- Dropdown menu {x=3,y=0,width=1,height=1,class="dropdown",name="an", items={"an1","an2","an3","an4","an5","an6","an7","an8","an9"},value="an8"},

-- Colour {x=4,y=0,width=1,height=1,class="color",name="c1"},

The colours come in this format: "#000000" in RRGGBB order. The actual tag is "&H000000&" in BBGGRR order, so you have to transform the result, for example like this: colour1=res.c1:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&")

Debugging / logging

This is very useful when you're getting errors. If you want to find where exactly your script has failed, you can use aegisub.log. There are two main ways to use it. One is to check whether the script passed a certain point, and another is to check a specific value.

The first one will usually go after then, like this: if text:match("\\blur") then aegisub.log("check") -- function continues after this

If this "check" gets logged, you know the condition has been met, ie. "\blur" was found in the text. This tells you how far the function has gone before something broke and helps you narrow down where the problem is.

The second way is for checking the value of something. aegisub.log("abc: "..abc)

The first part is just text, so that you know what's being logged if you're using several logs. The part after .. is the value of the variable called "abc". I often log multiple things when testing/debugging, and it gets chaotic unless each log is on a new line, so I automatically put "\n" into each log: aegisub.log("\n text: "..text)

This would be the most common one. Usually you work with text and make changes to it, so this shows you which changes did or didn't happen. So if you're getting errors, use logging to find out what exactly breaks and where.

Various stuff

-- duration of a line dur=line.end_time-line.start_time

-- character count -- this counts the string length after removing comments/tags. you can add :gsub(" ","") to not count spaces. visible=text:gsub("{[^}]-}","") characters=visible:len()

-- working with the line after the current one -- if you're on the last line, there is no next line and you'd get an error, thus the condition if i~=#subs then nextline=subs[i+1] end

-- working with previous line -- previous line always exists, but the one before the first dialogue line would be a style or something prevline=subs[i-1] if prevline.class=="dialogue" then blabla end -- counting stuff. "count=0" must be at the beginning, before the for loop. count=0 -- then in the main function: if text:match("stuff") then count=count+1 end -- at the end, after the for loop, you can log the result like this: aegisub.log("Stuff apears "..count.." times.")

-- error messages to the user -- if a script requires \pos tag and the user runs it on a line that doesn't have one, you can do this: if not text:match("\\pos") then aegisub.dialog.display({{class="label", label="No \\pos tag found.",x=0,y=0,width=1,height=2}},{"OK"}) aegisub.cancel() end

-- marking lines that have changed (running gsub doesn't tell you whether the pattern was found) text2=text text=text:gsub("\\1c&","\\c&") if text2~=text then effect=effect.."colour tag modified" end

-- a simple script to convert between clip and iclip -- you can't do one and then the other, because that would just convert the iclips you made back to clips -- therefore, you need elseif, which only comes to play if the first condition isn't met if text:match("\\clip") then text=text:gsub("\\clip","\\iclip") elseif text:match("\\iclip") then text=text:gsub("\\iclip","\\clip") end

Functions

Click here for some small functions that I've written (or got from someone, in a few cases) and that you can use.

Here is my Italicize script explained line by line. I figured I would take the smallest script I've made and explain it, but it kinda turns out that this one is actually pretty complicated and I could barely make sense of it myself (because it checks the style, deals with inline tags, checks for some mistakes, etc.). Anyway, I explained it as best I could, so hopefully it helps. (Every comment refers to what comes after it.)

This should be more than enough to get you started. Once you learn all this, you can figure out more from looking at existing scripts.

« Back to Typesetting Main Video Tutorials

Yep, it has come to this. Just like for timing, I made some videos for typesetting too.

Part 1 - about 10 signs in Namiuchigiwa no Muromi-san.

Download (76 MB, 16 minutes): Mediafire | Yandex

Some signs from one episode of Muromi. Everything from timing to final product, with typeset commentary. No mocha tracking, nothing too difficult. Styles are already created because looking for fonts would make the video 10 times longer and more boring.

Part 2 - 50 signs in Monogatari 2nd Season.

Download (70 MB, 21 minutes): Mediafire | Yandex

This will probably not teach you anything; it's more of a reference point to see what can be done and maybe fun to watch. I took first 50 signs (there are usually 100-200 per episode) and timed & typeset those in 20 minutes. I use scripts that I specifically wrote for Monogatari and the efficiency is almost ridiculous.

Part 3 - 3 signs in Kami Nomi zo Shiru Sekai: Megami Hen.

Download (45 MB, 12 minutes): Mediafire | Yandex

This one is pretty simple and slow, showing some rather basic stuff.

Part 4 - 6 signs in Kami Nomi zo Shiru Sekai: Megami Hen.

Download (56 MB, 13 minutes): Mediafire | Yandex

Some glow, some perspective, and several moving signs. Tips and different ways to do things.

Part 5 - Hyperdimensional Relocator.

Download (33 MB, 15 minutes): Mediafire | Yandex

How to use this script and what you can do with it.

Bonus - 60 signs in Monogatari 2nd Season.

Download (75 MB, 14 minutes): Mediafire

I recorded Monogatari the next week as well, just for the heck of it. It covers pretty much the whole episode except mocha tracking/clipping for the last sign in the video and 2 more signs (Years Earlier / Present). This video is played at 2x speed, so what you see took about 28 minutes. The remaining two signs probably took at least as much and ended up looking pretty good with the effect I used. Also, this one has no commentary and is not very educational at all. « Back to Typesetting Main unanimated's Aegisub scripts

» Updates / changelog here « · · · » Manuals for all reanimated scripts here « · · · » List of (almost) all functions here «

You can report bugs or make requests in #[email protected]

Scripts with GUI: Script Cleanup v3.4 - removes comments and other unneeded stuff from selected lines Blur and Glow v2.5 - creates layers with blur and/or glow HYDRA v5.0 - adds tags, does transforms, plus a bunch of special functions Hyperdimensional Relocator v4.0 - anything you need to do with pos, move, org, clip, and more Recalculator v3.0 - recalculates values of selected tags (multiply or add) Colorize v4.5 - does all kinds of things with colours Selectricks v2.8 - selects and sorts lines based on a bunch of criteria Multi-Line Editor v1.7 - allows you to edit multiple lines at once easily MultiCopy v3.4 - copy various things from multiple lines to other multiple lines Apply Fade v3.9 - does fades including alpha or colour transforms and more Significance v3.0 - does a shitload of things that didn't fit into other scripts ShiftCut v2.9 - does things with timing. TPP functions and more. Time Signs v2.8 - times signs from timecodes like {TS 5:36} Change Case v3.0 - lowercase, uppercase, capitalise lines or words

Multifunctions: Masquerade v2.5 - mask, shift tags, alignment, motion blur, merge tags, alpha shift, alpha time, strikealpha NecrosCopy v3.0 - fax, copy stuff, copy tags, copy text, copy clip, copy colours, 3D shadow, split by \N

Scripts without GUI: Cycles v1.8 - adds tags & cycles through values for blur, bord, shad, alpha, an [hotkey for each] iBus v1.7 - handles italics, bold, underline, and strikeout Line Breaker v2.3 - inserts and shifts linebreaks Join / Split / Snap v1.2 - joins lines / split lines / snaps to keyframes like TPP Jump to Next v1.2 - jumps to the next 'sign' Aladin's Lamp v1.1 - helps dealing with typesetting in Arabic

Encoding/muxing scripts: Encode - Hardsub v1.2 - encodes and hardsubs, whole video or clip Multiplexer v1.1 - muxes loaded script + video, chapters, creates CRC + xdelta

Extras: Runemap v1.0 - a map of moonrunes Backup Checker v1.1 - lets you to make a backup of your script and compare new lines with the backup

Scripts with GUI - obsolete: Modify Text v1.1 - [included in various other scripts] Make Chapters v1.0 - [included in Unimportant] Re-Split v1.1 - [included in Multi-Line Editor] Convert Framerate v1.0 - [included in Unimportant] Turn fade into transform v1.7 - [included in Apply fade] Back and Forth Transform v1.0 - [improved version included in HYDRA] Show text letter by letter v1.1 - [improved version included in Apply fade] Song Styler v1.1 - [not needed, covered by HYDRA] Add Mask v1.5 - [included in Masquerade] Alpha Timer v1.1 - [included in Masquerade] Alignment v1.0 - [included in HYDRA] Add fax v1.0 - [included in HYDRA] Bottom Alpha v1.1 - [included in 'Blur and Glow'] Add Animated Transform v1.0 - [included in HYDRA] Teleporter v1.3 - [merged into Hyperdimensional Relocator] Gradient Shifter v1.0 - [a rather useless variation of Teleporter] Copy Coordinates v1.2 - [merged into Hyperdimensional Relocator] Position Adjuster v1.2 - [merged into Hyperdimensional Relocator] Multicolour v1.0 - [included in HYDRA] Transform Alpha v1.2 - [covered by HYDRA and Apply fade] Transform clip v1.3 - [merged into Hyperdimensional Relocator] Add Scaling for Mocha v1.0 - [included in Masquerade] Add glow / Raise layer v1.0 - [replaced by 'Blur and Glow'] XY BorderShadow v1.0 - [included in HYDRA]

Without GUI (These are included in the bigger ones, but if you want quick, one-hotkey solutions...) Duplicate and Shift v1.0 - the old ctrl+D function [advanced version in Unimportant] Time signs from timecodes v1.3 - [old version without GUI] Blur Cycle v1.7 - [included in Cycles] Border Cycle v1.7 - [included in Cycles] Shadow Cycle v1.7 - [included in Cycles] Alpha Cycle v1.7 - [included in Cycles] Multimove v1.1 - [included in Hyperdimensional Relocator] Shadow 3D Effect v1.0 - [included in Copyfax This and HYDRA] Split by Linebreaks v1.5 - [included in CopyFax This] StrikeAlpha v1.1 - [included in Masquerade] Copy Tags v1.0 - [included in CopyFax This] Convert clip to drawing v1.2 - [included in HYDRA] Clip to iclip v1.0 - [included in HYDRA] Match fscy to fscx v1.0 - [included in HYDRA] Add \an8 v1.2 - [included in HYDRA and Masquerade] Add \q2 v1.2 - [included in HYDRA and Masquerade] Honorificslaughterhouse v??? - [included in Unimportant]

Mostly useless... Style Switch v1.0 - switches to the next style Reset Style v1.0 - use with \r to get a menu of styles to set Reset-to-tags v1.0 - changes \r[style] to tags with the [style]'s properties Rotated Shadow v1.0 - i don't even remember what this does ShadowyBorderx v1.1 - changes \bord to \xbord \ybord and same with shad

Old Versions of scripts, if for whatever reason you need one. If you happen to have any that I don't have anymore, link me.

Fun stuff: Properties chart | History

Images for the GUIs:

--[=[ If a full path is provided, that config file will always be used. If a filename is provided, then we attempt to open that file in the script directory, and if that fails, then we open it in the aegisub userdata directory (%APPDATA%/Aegisub or ~/.aegisub). This allows different settings (prefix, etc) per-project if you desire. If you don't trust any of this crazy shit, then config_file = false will disable all config related operations.]=]-- config_file = "aegisub-motion.conf" --[=[ YOU ARE LEGALLY BOUND AND GAGGED BY THE TERMS AND CONDITIONS OF THE LICENSE, EVEN IF YOU HAVEN'T READ THEM. ]=]--

--[=[ I THOUGHT I SHOULD PROBABLY INCLUDE SOME LICENSING INFORMATION IN THIS BUT I DON'T REALLY KNOW VERY MUCH ABOUT COPYRIGHT LAW AND IT ALSO SEEMS LIKE MOST COPYRIGHT NOTICES JUST KIND OF YELL AT YOU IN ALL CAPS. AND APPARENTLY DOES NOT EXIST IN ALL COUNTRIES, SO I FIGURED I'D STICK THIS HERE SO YOU KNOW THAT YOU, HENCEFORTH REFERRED TO AS "THE USER" HAVE THE FOLLOWING INALIABLE RIGHTS:

0. THE USER should realize that starting a list with 0 in a document that contains lua code is actually SOMEWHAT IRONIC. 1. THE USER can use this piece of poorly written code, henceforth referred to as THE SCRIPT, to do the things that it claims it can do. 2. THE USER should not expect THE SCRIPT to do things that it does not expressly claim to be able to do, such as make coffee or print money. 3. THE WRITER, henceforth referred to as I or ME, depending on the context, holds no responsibility for any problems that THE SCRIPT may cause, such as if it murders your dog. 4. THE USER is expected to understand that this is just some garbage that I made up and that any and all LEGALLY BINDING AGREEMENTS THAT THE USER HAS AGREED TO UPON USAGE OF THE SCRIPT ARE UP TO THE USER TO DISCOVER ON HIS OR HER OWN, POSSIBLY THROUGH CLAIRVOYANCE OR MAYBE A SPIRITUAL MEDIUM. 5. For fear of someone else attempting to steal my INTELLECTUAL PROPERTY, which is the result of MY OWN PERSONAL EFFORT and has come at the consequence of the EVAPORATION of ALL OF MY FREE TIME, I have decided to make ARBITRARY PARTS of this script PROPRIETARY CODE that THE USER IS ABSOLUTELY AND EXPLICITLY VERBOTEN FROM LOOKING AT AT ANY TIME. 6. This LICENSE AGREEMENT, which is IMPLICITLY AGREED TO upon usage of the script, regardless of whether or not THE USER has actually read it, IS RETROACTIVELY EXTENSIBLE. This means that ANY SUBSEQUENT TERMS ADDED TO IT IMMEDIATELY APPLY TO ALL OF THE USER'S ACTIONS IN THE PAST, and THE USER should be VERY CAREFUL that they have not previously VIOLATED any FUTURE TERMS AND CONDITIONS lest they be legally OPPRESSED by ME in a COURT OF LAW. 7. Should THE SCRIPT turn out to secretly be a cleverly disguised COMPUTER VIRUS in disguise, THE USER has agreed that any or all information it has gathered hereby belongs to ME and I CLAIM FULL RIGHTS TO IT, INCLUDING THE RIGHT TO REDISTRIBUTE IT AS I SEE FIT. THE USER also agrees to make NO PREVENTATIVE MEASURES to keep HIS OR HER computer from becoming PART OF THE BOTNET HIVEMIND. FURTHERMORE, THE USER agrees to take FULL PERSONAL RESPONSIBILITY for ANY ILLEGAL ACTIVITIES that HIS OR HER computer partakes in while under the CONTROL OF THE BOTNET. 8. This is an IMPORTANT NOTIFICATION, you should try to defraud SOME STUPID WIG posing as THE AUTHOR OF THIS SOFTWARE, you will be hunted down to a REASONABLE RABIES WOLF, in a timely manner to THE MURDER OF YOUR PACKAGE, and then eat YOUR BODY. There will be ANY OF THE AUTHORITIES to find you the possibility of leaving are VERY SLIM, even IN THE UNLIKELY EVENT THIS DOES OCCUR, will have to cope with the MURDER. In addition, I HAPPEN TO HAVE an independent country, DO NOT CARE ABOUT THE LITTLE THINGS, such as THE MURDER OF A BEAUTIFUL APARTMENT. Besides, I have MY LAWYER to prosecute THE GOOD NAME OF YOUR DISTRAUGHT FAMILY, you have a stain, so I CHANGE FROM THIRD PERSON TO FIRST PERSON HARM, but I think this subtlety will be lost to Google Translate. In short, FUCK YOU. 9. THE USER understands that while the inclusion of a CHINESE MOONRUNE CLAUSE in the LICENSE AGREEMENT was VITALLY IMPORTANT, it unfortunately HAD TO BE REMOVED because THE LUA PARSER IS EVER SO FRAGILE and has been known to do VERY CONFUSING THINGS in the face of MULTIBYTE CHARACTERS, even when THE SCRIPT is encoded as UTF-8. A HIGH QUALITY translation of the PREVIOUS TERM HAS BEEN SUBSTITUTED IN for the FORESEEABLE FUTURE. Should it raise ANY QUESTIONS, THE USER is welcome to JUST GO AHEAD AND JUMP OFF OF A BRIDGE because his or her stupidity is OBVIOUSLY INCURABLE. 10. THE USER must understand the difference between a COPYRIGHT LICENSE and an END-USER LICENSE AGREEMENT. COPYRIGHT LICENSES are THE THINGS that get put ON TOP of A PIECE OF CODE that tell people that YOU ARE NOT LEGALLY ALLOWED TO REDISTRIBUTE THIS FILE UNLESS YOU HAVE RECENTLY CASTRATED YOURSELF WITH A SPORK and even then only under SPECIFIC CIRCUMSTANCES. END-USER LICENSE AGREEMENTS are THE UNREADABLE WALLS OF LEGALESE that HUMONGOUS, PROFITABLE CORPORATIONS pay LEGIONS OF LEGAL PERSONELLE to develop that tell you that YOU ARE NOT LEGALLY ALLOWED TO USE THE SOFTWARE YOU JUST INSTALLED UNLESS YOU WILLINGLY CONSIGN YOUR ENTIRE ESTATE TO SAID CORPORATION IN YOUR LAST WILL AND TESTAMENT. ]=]--

--[[ Global definitions for Aegisub. ]]-- script_name = "Aegisub-Motion" script_description = "A set of tools for simplifying the process of creating and applying motion tracking data with Aegisub." -- and it might have memory issues. I think. script_author = "torque" script_version = "2.0.0.0.0.0-2" -- PATCHLEVEL BUMP?!

--[[ Include helper scripts. ]]-- require "karaskel" if not pcall(require, "clipboard") then error("Aegisub 3.0.0 or better is required.") end if not pcall(require, "debug") then dbg = false end

--[[ Alias commonly used functions with much shorter identifiers. As an added bonus, this makes the code more confusing. ]]-- dcp = aegisub.decode_path sc = string.char

--[[ Detect whether to use *nix or Windows style paths. ]]-- winpaths = not dcp('?data'):match('/')

--[[ Set up interface tables. ]]-- gui = { main = { linespath = { class = "textbox"; name = "linespath"; hint = "Paste data or the path to a file containing it. No quotes or escapes."; x = 0; y = 1; height = 4; width = 10;}, pref = { class = "textbox"; name = "pref"; hint = "The prefix"; x = 0; y = 14; height = 3; width = 10; hint = "The directory any generated files will be written to."}, preflabel = { class = "label"; label = " Files will be written to this directory."; x = 0; y = 13; height = 1; width = 10;}, datalabel = { class = "label"; label = " Paste data or enter a filepath."; x = 0; y = 0; height = 1; width = 10;}, optlabel = { class = "label"; label = "Data to be applied:"; x = 0; y = 6; height = 1; width = 5;}, rndlabel = { class = "label"; label = "Rounding"; x = 7; y = 6; height = 1; width = 3;}, xpos = { class = "checkbox"; name = "xpos"; value = true; label = "x"; x = 0; y = 7; height = 1; width = 1; hint = "Apply x position data to the selected lines."}, ypos = { class = "checkbox"; name = "ypos"; value = true; label = "y"; x = 1; y = 7; height = 1; width = 1; hint = "Apply y position data to the selected lines."}, origin = { class = "checkbox"; name = "origin"; value = false; label = "Origin"; x = 2; y = 7; height = 1; width = 2; hint = "Move the origin along with the position."}, clip = { class = "checkbox"; name = "clip"; value = false; label = "Clip"; x = 4; y = 7; height = 1; width = 2; hint = "Move clip along with the position (note: will also be scaled and rotated if those options are selected)."}, scale = { class = "checkbox"; name = "scale"; value = true; label = "Scale"; x = 0; y = 8; height = 1; width = 2; hint = "Apply scaling data to the selected lines."}, border = { class = "checkbox"; name = "border"; value = true; label = "Border"; x = 2; y = 8; height = 1; width = 2; hint = "Scale border with the line (only if Scale is also selected)."}, shadow = { class = "checkbox"; name = "shadow"; value = true; label = "Shadow"; x = 4; y = 8; height = 1; width = 2; hint = "Scale shadow with the line (only if Scale is also selected)."}, blur = { class = "checkbox"; name = "blur"; value = true; label = "Blur"; x = 4; y = 9; height = 1; width = 2; hint = "Scale blur with the line (only if Scale is also selected; does not scale \\be)."}, rotation = { class = "checkbox"; name = "rotation"; value = false; label = "Rotation"; x = 0; y = 9; height = 1; width = 3; hint = "Apply rotation data to the selected lines."}, posround = { class = "intedit"; name = "posround"; value = 2; min = 0; max = 5; x = 7; y = 7; height = 1; width = 3; hint = "How many decimal places of accuracy the resulting positions should have."}, sclround = { class = "intedit"; name = "sclround"; value = 2; min = 0; max = 5; x = 7; y = 8; height = 1; width = 3; hint = "How many decimal places of accuracy the resulting scales should have (also applied to border, shadow, and blur)."}, rotround = { class = "intedit"; name = "rotround"; value = 2; min = 0; max = 5; x = 7; y = 9; height = 1; width = 3; hint = "How many decimal places of accuracy the resulting rotations should have."}, wconfig = { class = "checkbox"; name = "wconfig"; value = false; label = "Write config"; x = 0; y = 11; height = 1; width = 4; hint = "Write current settings to the configuration file."}, relative = { class = "checkbox"; name = "relative"; value = true; label = "Relative"; x = 4; y = 11; height = 1; width = 3; hint = "Start frame should be relative to the line's start time rather than to the start time of all selected lines"}, stframe = { class = "intedit"; name = "stframe"; value = 1; x = 7; y = 11; height = 1; width = 3; hint = "Frame used as the starting point for the tracking data. \"-1\" corresponds to the last frame."}, linear = { class = "checkbox"; name = "linear"; value = false; label = "Linear"; x = 4; y = 12; height = 1; width = 2; hint = "Use transforms and \\move to create a linear transition, instead of frame-by-frame."}, sortd = { class = "dropdown"; name = "sortd"; hint = "Sort lines by"; value = "Default"; items = {"Default", "Time"}; x = 5; y = 5; width = 4; height = 1; hint = "The order to sort the lines after they have been tracked."}, sortlabel = { class = "label"; name = "sortlabel"; label = " Sort Method:"; x = 1; y = 5; width = 4; height = 1;}, }, clip = { clippath = { class = "textbox"; name = "clippath"; hint = "Paste data or the path to a file containing it. No quotes or escapes."; x = 0; y = 1; height = 4; width = 10;}, label = { class = "label"; label = " Paste data or enter a filepath."; x = 0; y = 0; height = 1; width = 10;}, xpos = { class = "checkbox"; name = "xpos"; value = true; label = "x"; x = 0; y = 6; height = 1; width = 1; hint = "Apply x position data to the selected lines."}, ypos = { class = "checkbox"; name = "ypos"; value = true; label = "y"; x = 1; y = 6; height = 1; width = 1; hint = "Apply y position data to the selected lines."}, scale = { class = "checkbox"; name = "scale"; value = true; label = "Scale"; x = 0; y = 7; height = 1; width = 2;}, rotation = { class = "checkbox"; name = "rotation"; value = false; label = "Rotation"; x = 0; y = 8; height = 1; width = 3;}, relative = { class = "checkbox"; name = "relative"; value = true; label = "Relative"; x = 4; y = 6; height = 1; width = 3;}, stframe = { class = "intedit"; name = "stframe"; value = 1; x = 7; y = 6; height = 1; width = 3;}, } }

--[[ Set up encoder presets. ]]-- encpre = { x264 = '"#{encbin}" --crf 16 --tune fastdecode -i 250 --fps 23.976 --sar 1:1 --index "#{prefix}#{index}.index" -- seek #{startf} --frames #{lenf} -o "#{prefix}#{output}[#{startf}-#{endf}].mp4" "#{inpath}#{input}"', ffmpeg = '"#{encbin}" -ss #{startt} -t #{lent} -sn -i "#{inpath}#{input}" "#{prefix}#{output}[#{startf}-#{endf}]- %05d.jpg"', avs2yuv = 'echo FFVideoSource("#{inpath}#{input}",cachefile="#{prefix}#{index}.index").trim(#{startf},#{endf}).ConvertToRGB.Im ageWriter("#{prefix}#{output}-[#{startf}-#{endf}]\\",type="png").ConvertToYV12 > "#{prefix}encode.avs"#{nl}mkdir "#{prefix}#{output}-[#{startf}-#{endf}]"#{nl}"#{encbin}" -o NUL "#{prefix}encode.avs"#{nl}del "#{prefix}encode.avs"', -- vapoursynth = }

--[[ Set up a table of global options. Defaults included. ]]-- global = { prefix = "?video", encoder = "x264", -- todo: move to trim options encbin = "", -- same gui_trim = false, -- same autocopy = true, acfilter = true, delsourc = false, } --[[ Set encoding command default based on preset. ]]-- global.enccom = encpre[global.encoder] or ""

--[[ Copy the main GUI with some modifications for the config GUI. Helps to lower the amount of code duplication (???) ]]-- gui.conf = table.copy_deep(gui.main) gui.conf.clippath, gui.conf.linespath, gui.conf.wconfig = nil gui.conf.encbin, gui.conf.pref = table.copy(gui.conf.pref), nil gui.conf.encbin.value, gui.conf.encbin.name = global.encbin, "encbin" gui.conf.encbin.hint = "The full path to the encoder binary (unless it's in your PATH)" gui.conf.datalabel.label = " Enter the path to your prefix here (include trailing slash)." gui.conf.preflabel.label = "First box: path to encoder binary; second box: encoder command." gui.conf.gui_trim = { class = "checkbox"; value = global.gui_trim; label = "Enable trim GUI"; name = "gui_trim"; x = 3; y = 22; height = 1; width = 4; hint = "Set whether or not the trim gui should appear."} gui.conf.enccom = { class = "textbox"; value = global.enccom; name = "enccom"; x = 0; y = 17; height = 4; width = 10; hint = "The encoding command that will be used. If you change this, set the preset to \"custom\"."} gui.conf.prefix = { class = "textbox"; value = global.prefix; name = "prefix"; x = 0; y = 1; height = 4; width = 10; hint = "The folder to which all generated files will be written."} gui.conf.encoder = { class = "dropdown"; value = global.encoder; name = "encoder"; items = {"x264", "ffmpeg", "avs2yuv", "custom"}; x = 0; y = 11; height = 1; width = 2; hint = "Choose one of the encoding command presets (set to custom if you have made any modifications to the defaults)"} gui.conf.delsourc = { class = "checkbox"; value = global.delsourc; label = "Delete"; name = "delsourc"; x = 0; y = 21; height = 1; width = 2; hint = "Delete the source lines instead of commenting them out."} gui.conf.autocopy = { class = "checkbox"; value = global.autocopy; label = "Autocopy"; name = "autocopy"; x = 3; y = 21; height = 1; width = 3; hint = "Automatically copy the contents of the clipboard into the tracking data box on script run."} gui.conf.acfilter = { class = "checkbox"; value = global.acfilter; label = "Copy Filter"; name = "acfilter"; x = 7; y = 21; height = 1; width = 3; hint = "Only automatically copy the clipboard if it appears to contain tracking data."}

--[[ A table of all override tags that can be looped through. For detecting dupes in cleanup. ]]-- alltags = { xscl = "\\fscx([%d%.]+)", yscl = "\\fscy([%d%.]+)", ali = "\\an([1-9])", zrot = "\\frz?([%-%d%.]+)", bord = "\\bord([%d%.]+)", xbord = "\\xbord([%d%.]+)", ybord = "\\ybord([%d%.]+)", shad = "\\shad([%-%d%.]+)", xshad = "\\xshad([%-%d%.]+)", yshad = "\\yshad([%-%d%.]+)", reset = "\\r([^\\}]*)", alpha = "\\alpha&H(%x%x)&", l1a = "\\1a&H(%x%x)&", l2a = "\\2a&H(%x%x)&", l3a = "\\3a&H(%x%x)&", l4a = "\\4a&H(%x%x)&", l1c = "\\c&H(%x+)&", l1c2 = "\\1c&H(%x+)&", l2c = "\\2c&H(%x+)&", l3c = "\\3c&H(%x+)&", l4c = "\\4c&H(%x+)&", clip = "\\clip%((.-)%)", iclip = "\\iclip%((.-)%)", be = "\\be([%d%.]+)", blur = "\\blur([%d%.]+)", fax = "\\fax([%-%d%.]+)", fay = "\\fay([%-%d%.]+)" } globaltags = { fad = "\\fad%([%d]+,[%d]+%)", fade = "\\fade%(([%d]+),([%d]+),([%d]+),([%-%d]+),([%-%d]+),([%-%d]+),([%-%d]+)%)", clip = "" }

--[[ This is a rather messy table of tags that is used to verify that style defaults are inserted at the beginning the selected line(s) if the corresponding options are selected. The structure is: [tag] = {{"opt1","opt2"}, "style key", don't write} where "opt1" and "opt2" are the options that both must be true, "style key" is the key to get the style value, and don't write specifies not to write the tag if the style default is that value. ]]-- importanttags = { -- scale_x, scale_y, outline, shadow, angle ['\\fscx'] = {{"scale","scale"}, "scale_x", 0}; ['\\fscy'] = {{"scale","scale"}, "scale_y", 0}; ['\\bord'] = {{"border","scale"}, "outline", 0}; ['\\shad'] = {{"shadow","scale"}, "shadow", 0}; ['\\frz'] = {{"rotation","rotation"}, "angle"}; }

--[[ A table of config keys whose values should be written to the configurtion file. structure is [header] = {keys...} ]]-- guiconf = { main = { "sortd", "xpos", "ypos", "origin", "clip", "posround", "scale", "border", "shadow", "blur", "sclround", "rotation", "rotround", "relative", "stframe", "linear", --"export", }, clip = { "xpos", "ypos", "scale", "rotation", "relative", "stframe", }, }

--[[ Stick the global config keys in the above table. ]]-- for k,v in pairs(global) do table.insert(guiconf,k) end

--[[ Functions for more easily handling angles specified in degrees ]]-- function dcos(a) return math.cos(math.rad(a)) end function dacos(a) return math.deg(math.acos(a)) end function dsin(a) return math.sin(math.rad(a)) end function dasin(a) return math.deg(math.asin(a)) end function dtan(a) return math.tan(math.rad(a)) end function datan(y,x) return math.deg(math.atan2(y,x)) end

--[[ Functions for giving the default position of a line, given its alignment and margins. The alignment can be split into x and y as follows: x = an%3+1 -> 1 = right aligned (3,6,9), 2 = left aligned (1,4,7), and 3 = centered (2,5,8); y = math.ceil(an/3) -> 1 = bottom (1,2,3), 2 = middle (4,5,6), 3 = top (7,8,9). In the below functions, `sx` is the script width, `sy` is the script height, `l` is the line's left margin, `r` is the line's right margin, and `v` is the line's vertical margin. ]]-- fix = { xpos = { function(sx,l,r) return sx-r end; function(sx,l,r) return l end; function(sx,l,r) return sx/2 end; }, ypos = { function(sy,v) return sy-v end; function(sy,v) return sy/2 end; function(sy,v) return v end; }, } function readconf(conf,guitab) local valtab = {} aegisub.log(5,"Opening config file: %s\n",conf) local cf = io.open(conf,'r') if cf then aegisub.log(5,"Reading config file...\n") local thesection for line in cf:lines() do local section = line:match("#(%w+)") if section then valtab[section] = {} thesection = section aegisub.log(5,"Section: %s\n",thesection) elseif thesection == nil then return nil else local key, val = line:splitconf() aegisub.log(5,"Read: %s -> %s\n", key, tostring(val:tobool())) valtab[thesection][key:gsub("^ +","")] = val:tobool() end end cf:close() convertfromconf(valtab,guitab) return true else return nil end end function convertfromconf(valtab,guitab) --aegisub.log(5,"%s\n",table.tostring(guitab)) for section,sectab in pairs(guitab) do for ident,value in pairs(valtab[section]) do if section == "global" then aegisub.log(5,"Set: global.%s = %s (%s)\n",ident,tostring(value),type(value)) sectab[ident] = value else if sectab[ident] then aegisub.log(5,"Set: gui.%s.%s = %s (%s)\n",section,ident,tostring(value),type(value)) sectab[ident].value = value end end end end end function writeconf(conf,optab) local cf = io.open(conf,'w+') if not cf then aegisub.log(0,'Config write failed! Check that %s exists and has write permission.\n',cf) return nil end local configlines = {} for section,tab in pairs(optab) do table.insert(configlines,("#%s\n"):format(section)) if section == "global" then for ident,value in pairs(tab) do table.insert(configlines,(" %s:%s\n"):format(ident,tostring(value))) end else for i, field in ipairs(guiconf[section]) do if tab[field] ~= nil then -- (e.g. when clipconf == {}, don't overwrite all the config with "nil") table.insert(configlines,(" %s:%s\n"):format(field,tostring(tab[field]))) end end end end for i,v in ipairs(configlines) do aegisub.log(5,"Write: %s -> config\n",v:gsub("^ +","")) cf:write(v) end cf:close() aegisub.log(5,"Config written to %s\n",conf) return true end function configscope() local cf if tostring(config_file):match("^[A-Z]:\\") or tostring(config_file):match("^/") or not config_file then return config_file else cf = io.open(dcp("?script/"..config_file)) if not cf then cf = dcp("?user/"..config_file) else cf:close() cf = dcp("?script/"..config_file) end return cf end end function string:splitconf() local line = self:gsub("[\r\n]*","") return line:match("^(.-):(.*)$") end function string:tobool() local bool = ({['true'] = true, ['false'] = false})[self] if bool ~= nil then return bool else return self end end function fixFad(text) local firstfad = text:match(globaltags.fad) if firstfad and not text:match("^{(.-)}"):match(globaltags.fad) then text:gsub(globaltags.fad,"") text = ("{%s}%s"):format(firstfad,sc(6)) text:gsub("}"..sc(6).."{","") end return text end function extraLineMetrics(line) line.trans = {} local fstart,fend = line.text:match("\\fad%((%d+),(%d+)%)") local alphafunc = "\\alpha%1" local function lextrans(trans) t_start,t_end,t_exp,t_eff = trans:sub(2,-2):match("([%-%d]+),([%-%d]+),([%d%.]*),?(.+)") t_exp = tonumber(t_exp) or 1 -- set to 1 if unspecified table.insert(line.trans,{tonumber(t_start),tonumber(t_end),t_exp,t_eff}) aegisub.log(5,"Line %d: \\t(%g,%g,%g,%s) found\n",line.hnum,t_start,t_end,t_exp,t_eff) end line.text = line.text:gsub("^({.-})", function(block1) if fstart then local replaced = false alphafunc = function(alpha) local str = "" if tonumber(fstart) > 0 then str = str..("\\alpha&HFF&\\t(%d,%s,1,\\alpha%s)"):format(0,fstart,alpha) end if tonumber(fend) > 0 then str = str..("\\t(%d,%d,1,\\alpha&HFF&)"):format(line.duration-tonumber(fend),line.duration) end aegisub.log(5,str..'\n') return str end block1 = block1:gsub("\\alpha(&H%x%x&)",function(alpha) replaced = true; return alphafunc(alpha) end) block1 = block1:gsub(globaltags.fad,function() if not replaced then return alphafunc(alpha_from_style(line.styleref.color1)) else return "" end end) else block1 = block1:gsub("\\fade%(([%d]+),([%d]+),([%d]+),([%-%d]+),([%-%d]+),([%-%d]+),([%- %d]+)%)",function(a,b,c,d,e,f,g) return ("\\alpha&H%02X&\\t(%s,%s,1,\\alpha&H%02X&)\\t(%s,%s,1,\\alpha&H%02X&)"):format(a,d,e,b,f,g,c) end) end block1:gsub("\\t(%b())",lextrans) return block1 end) line.text = line.text:gsub("([^^])({.-})",function(i,block) block = block:gsub("\\alpha(&H%x%x&)",alphafunc) block:gsub("\\t(%b())",lextrans) return i..block end) line.text = line.text:gsub("\\(i?clip)(%b())",function(clip,points) line.clips = clip points = points:gsub("([%-%d]+),([%-%d]+),([%-%d]+),([%-%d]+)", function (leftX,topY,rightX,botY) return ("m %s %s l %s %s %s %s %s %s"):format(leftX,topY,rightX,topY,rightX,botY,leftX,botY) end,1) points:gsub("%(([%d]*),?(.-)%)",function(scl,clip) line.sclip = tonumber(scl) or 1 if tonumber(scl) then line.rescaleclip = true end line.clip = clip end,1) return '\\'..clip..points end) return line end function getSelInfo(sub, sel) printmem("Initial") local strt for x = 1,#sub do -- so if there are like 10000 different styles then this is probably a really bad idea but I DON'T GIVE A FUCK if sub[x].class == "dialogue" then -- BECAUSE I SAID SO strt = x-1 -- start line of dialogue subs break end end aegisub.progress.title("Collecting Gerbils") local accd = {} local _ = nil accd.meta, accd.styles = karaskel.collect_head(sub, false) -- dump everything I need later into the table so I don't have to pass o9k variables to the other functions accd.lines = {} accd.endframe = aegisub.frame_from_ms(sub[sel[1]].end_time) -- get the end frame of the first selected line accd.startframe = aegisub.frame_from_ms(sub[sel[1]].start_time) -- get the start frame of the first selected line local numlines = #sel for i = #sel,1,-1 do -- burning cpu cycles like they were no thing local line = sub[sel[i]] line.num = sel[i] -- for inserting lines later line.hnum = line.num-strt -- humanized number karaskel.preproc_line(sub, accd.meta, accd.styles, line) -- get linewidth/height and margins if not line.effect then line.effect = "" end sub[sel[i]] = extraLineMetrics(line) line.startframe, line.endframe = aegisub.frame_from_ms(line.start_time), aegisub.frame_from_ms(line.end_time) if line.comment then line.is_comment = true else line.is_comment = false end if line.startframe < accd.startframe then -- make timings flexible. Number of frames total has to match the tracked data but aegisub.log(5,"Line %d: startframe changed from %d to %d\n",line.num-strt,accd.startframe,line.startframe) accd.startframe = line.startframe end if line.endframe > accd.endframe then -- individual lines can be shorter than the whole scene aegisub.log(5,"Line %d: endframe changed from %d to %d\n",line.num-strt,accd.endframe,line.endframe) accd.endframe = line.endframe end if line.endframe-line.startframe>1 then table.insert(accd.lines,line) end end local length = #accd.lines accd.totframes = accd.endframe - accd.startframe assert(#accd.lines>0,"You have to select at least one line that is longer than one frame long.") -- pro error checking printmem("End of preproc loop") return accd end function populateInputBox() if global.autocopy then local paste = clipboard.get() or "" --if there's nothing on the clipboard, clipboard.get retuns nil if global.acfilter then if paste:match("^ 6.0 Keyframe Data") then gui.main.linespath.value = paste end else gui.main.linespath.value = paste end end end function dialogPreproc(sub,sel) aegisub.progress.title("Selecting Gerbils") local accd = getSelInfo(sub,sel) --preprocessing(sub,sel) gui.main.stframe.min = -accd.totframes; gui.main.stframe.max = accd.totframes; gui.clip.stframe.min = -accd.totframes; gui.clip.stframe.max = accd.totframes; local conf = configscope() if conf then if not readconf(conf,{ ['main'] = gui.main; ['clip'] = gui.clip; ['global'] = global }) then aegisub.log(3,"Failed to read config!\n") end end populateInputBox() gui.main.pref.value = dcp(global.prefix) return conf, accd end function init_input(sub,sel) -- THIS IS PROPRIETARY CODE YOU CANNOT LOOK AT IT local setundo = aegisub.set_undo_point -- ugly workaround for a problem that was causing random crashes printmem("GUI startup") local conf, accd = dialogPreproc(sub,sel) local button, config = aegisub.dialog.display(gui.main, {"Go","Clip...","Abort"}) local clipconf if button == "Clip..." then button, clipconf = aegisub.dialog.display(gui.clip, {"Go","Cancel","Abort"}) end if button == "Go" then local clipconf = clipconf or {} -- solve indexing errors for i,field in ipairs(guiconf.clip) do if clipconf[field] == nil then clipconf[field] = gui.clip[field].value end end if config.linespath == "" then config.linespath = false end if config.wconfig then writeconf(conf,{ ['main'] = config; ['clip'] = clipconf; ['global'] = global }) end if config.stframe == 0 then config.stframe = 1 end -- TODO: fix this horrible clusterfuck if clipconf.stframe == 0 then clipconf.stframe = 1 end if config.xpos or config.ypos then config.position = true end config.yconst = not config.ypos; config.xconst = not config.xpos if clipconf.xpos or clipconf.ypos then clipconf.position = true end clipconf.yconst = not clipconf.ypos; clipconf.xconst = not clipconf.xpos -- TODO: remove unnecessary logic inversion if clipconf.clippath == "" or clipconf.clippath == nil then if not config.linespath then windowerr(false,"No tracking data was provided.") end clipconf.clippath = false else config.clip = false end -- set clip to false if clippath exists if config.clip then clipconf.stframe = config.stframe; config.linear = false end if clipconf.clippath then config.linear = false end aegisub.progress.title("Mincing Gerbils") printmem("Go") local newsel = frame_by_frame(sub,accd,config,clipconf) if munch(sub,newsel) then newsel = {} for x = 1,#sub do if tostring(sub[x].effect):match("^aa%-mou") then table.insert(newsel,x) end end end aegisub.progress.title("Reformatting Gerbils") cleanup(sub,newsel,config) elseif button == "Cancel" then init_input(sub,sel) -- this is extremely unideal as it reruns all of the information gathering functions as well. else aegisub.progress.task("ABORT") aegisub.cancel() end setundo("Motion Data") printmem("Closing") end function parse_input(mocha_table,input,shx,shy) printmem("Start of input parsing") local ftab = {} local sect, care = 0, 0 mocha_table.xpos, mocha_table.ypos, mocha_table.xscl, mocha_table.yscl, mocha_table.zrot = {}, {}, {}, {}, {} local datams = io.open(input,"r") -- a terrible idea? Doesn't seem to be so far. local datastring = "" if datams then for line in datams:lines() do line = line:gsub("[\r\n]*","") -- FUCK YOU CRLF datastring = datastring..line.."\n" table.insert(ftab,line) -- dump the lines from the file into a table. end datams:close() else input = input:gsub("[\r]*","") -- SERIOUSLY FUCK THIS SHIT datastring = input ftab = input:split("\n") end for _,pattern in ipairs({"Position","Scale","Rotation","Source Width\t%d+","Source Height\t%d+","Adobe After Effects 6.0 Keyframe Data"}) do windowerr(datastring:match(pattern),'Error parsing data. "After Effects Transform Data [anchor point, position, scale and rotation]" expected.') end local sw = datastring:match("Source Width\t([0-9]+)") local sh = datastring:match("Source Height\t([0-9]+)") local xmult = shx/tonumber(sw) local ymult = shy/tonumber(sh) for keys, valu in ipairs(ftab) do -- idk it might be more flexible now or something if not valu:match("^\t") then if valu == "Position" then sect = 1 elseif valu == "Scale" then sect = sect + 2 elseif valu == "Rotation" then sect = sect + 4 else end else if sect == 1 then if valu:match("%d") then val = valu:split("\t") table.insert(mocha_table.xpos,tonumber(val[2])*xmult) table.insert(mocha_table.ypos,tonumber(val[3])*ymult) end elseif sect == 3 then if valu:match("%d") then val = valu:split("\t") table.insert(mocha_table.xscl,tonumber(val[2])) table.insert(mocha_table.yscl,tonumber(val[3])) end elseif sect == 7 then if valu:match("%d") then val = valu:split("\t") table.insert(mocha_table.zrot,-tonumber(val[2])) end end end end mocha_table.flength = #mocha_table.xpos windowerr(mocha_table.flength == #mocha_table.ypos and mocha_table.flength == #mocha_table.xscl and mocha_table.flength == #mocha_table.yscl and mocha_table.flength == #mocha_table.zrot,'Error parsing data. "After Effects Transform Data [anchor point, position, scale and rotation]" expected.') for prefix,field in pairs({x = "xpos", y = "ypos", xs = "xscl", ys = "yscl", r = "zrot"}) do local dummytab = table.copy(mocha_table[field]) table.sort(dummytab) mocha_table[prefix.."max"], mocha_table[prefix.."min"] = dummytab[#dummytab], dummytab[1] aegisub.log(5,"%smax: %g; %smin: %g\n",prefix,mocha_table[prefix.."max"],prefix,mocha_table[prefix.."min"]) end printmem("End of input parsing") end function windowerr(bool, message) if not bool then aegisub.dialog.display({{class="label", label=message}},{"ok"}) aegisub.cancel() end end function spoof_table(parsed_table,opts,len) local len = len or #parsed_table.xpos parsed_table.xpos = parsed_table.xpos or {} parsed_table.ypos = parsed_table.ypos or {} parsed_table.xscl = parsed_table.xscl or {} parsed_table.yscl = parsed_table.yscl or {} parsed_table.zrot = parsed_table.zrot or {} if not opts.position then for k = 1,len do parsed_table.xpos[k] = 0 parsed_table.ypos[k] = 0 end else if opts.yconst then for k = 1,len do parsed_table.ypos[k] = 0 end end if opts.xconst then for k = 1,len do parsed_table.xpos[k] = 0 end end end if not opts.scale then for k = 1,len do parsed_table.xscl[k] = 100 parsed_table.yscl[k] = 100 end end if not opts.rotation then for k = 1,len do parsed_table.zrot[k] = 0 end end parsed_table.s = 1 if opts.reverse then parsed_table.s = parsed_table.flength end end function ensuretags(line,opts,styles,dim) if line.margin_v ~= 0 then line._v = line.margin_v else line._v = line.styleref.margin_v end if line.margin_l ~= 0 then line._l = line.margin_l else line._l = line.styleref.margin_l end if line.margin_r ~= 0 then line._r = line.margin_r else line._r = line.styleref.margin_r end line.ali = line.text:match("\\an([1-9])") or line.styleref.align line.xpos,line.ypos = line.text:match("\\pos%(([%-%d%.]+),([%-%d%.]+)%)") if not line.xpos then -- insert position into line if not present. line.xpos = fix.xpos[line.ali%3+1](dim.x,line._l,line._r) line.ypos = fix.ypos[math.ceil(line.ali/3)](dim.y,line._v) line.text = (("{\\pos(%d,%d)}"):format(line.xpos,line.ypos)..line.text):gsub("^({.-)}{","%1") end line.oxpos,line.oypos = line.text:match("\\org%(([%-%d%.]+),([%-%d%.]+)%)") line.oxpos = line.oxpos or line.xpos; line.oypos = line.oypos or line.ypos -- debug("arg: (%g,%g)\n",line.oxpos,line.oypos) line.origindx,line.origindy = line.xpos - line.oxpos, line.ypos - line.oypos local mergedtext = line.text:gsub("}{","") local startblock = mergedtext:match("^{(.-)}") local block = "" if startblock then for tag, str in pairs(importanttags) do if opts[str[1][1]] and opts[str[1][2]] and not startblock:match(tag.."[%-%d%.]+") then if tonumber(line.styleref[str[2]]) ~= str[3] then block = block..(tag.."%g"):format(line.styleref[str[2]]) end end end if block:len() > 0 then line.text = ("{"..block.."}"..line.text):gsub("^({.-)}{","%1") end else for tag, str in pairs(importanttags) do if opts[str[1][1]] and opts[str[1][2]] then if tonumber(line.styleref[str[2]]) ~= str[3] then block = block..(tag.."%g"):format(line.styleref[str[2]]) end end end line.text = "{"..block.."}"..line.text end function resetti(before,rstyle,rest) local styletab = styles[rstyle] or line.styleref -- if \\r[stylename] is not a real style, reverts to regular \r local block = "" for tag, str in pairs(importanttags) do if opts[str[1][1]] and opts[str[1][2]] and not startblock:match(tag.."[%-%d%.]+") then if tonumber(line.styleref[str[2]]) ~= str[3] then block = block..(tag.."%g"):format(styletab[str[2]]) end end end return "{"..before..rstyle..block..rest.."}" end line.text = line.text:gsub("{([^}]*\\r)([^\\}]*)(.-)}",resetti) end function frame_by_frame(sub,accd,opts,clipopts) printmem("Start of main loop") local dim = {x = accd.meta.res_x; y = accd.meta.res_y} local mocha = {} local clipa = {} if opts.linespath then parse_input(mocha,opts.linespath,accd.meta.res_x,accd.meta.res_y) assert(accd.totframes == mocha.flength,string.format("Number of frames selected (%d) does not match parsed line tracking data length (%d).",accd.totframes,mocha.flength)) spoof_table(mocha,opts) if not opts.relative then if opts.stframe < 0 then mocha.start = accd.totframes + opts.stframe + 1 else mocha.start = opts.stframe end end if opts.clip then clipa = mocha end end if clipopts.clippath then parse_input(clipa,clipopts.clippath,accd.meta.res_x,accd.meta.res_y) assert(accd.totframes == clipa.flength,string.format("Number of frames selected (%d) does not match parsed clip tracking data length (%d).",accd.totframes,clipa.flength)) opts.linear = false -- no linear mode with moving clip, sorry opts.clip = true -- simplify things a bit spoof_table(clipa,clipopts) if not opts.linespath then spoof_table(mocha,opts,#clipa.xpos) end if not clipopts.relative then if clipopts.stframe < 0 then clipa.start = accd.totframes + clipopts.stframe + 1 else clipa.start = clipopts.stframe end end end for k,v in ipairs(accd.lines) do -- comment lines that were commented in the thingy local derp = sub[v.num] derp.comment = true sub[v.num] = derp if not v.is_comment then v.comment = false end end local _ = nil local newlines = {} -- table to stick indices of tracked lines into for cleanup. local operations = {} -- create a table and put the necessary functions into it, which will save a lot of if operations in the inner loop. This was the most elegant solution I came up with. if opts.position then operations["(\\pos)%(([%-%d%.]+,[%-%d%.]+)%)"] = possify if opts.origin then operations["(\\org)%(([%-%d%.]+,[%-%d%.]+)%)"] = orginate end end if opts.scale then operations["(\\fsc[xy])([%d%.]+)"] = scalify if opts.border then operations["(\\[xy]?bord)([%d%.]+)"] = scalify end if opts.shadow then operations["(\\[xy]?shad)([%-%d%.]+)"] = scalify end if opts.blur then operations["(\\blur)([%d%.]+)"] = scalify end end if opts.rotation then operations["(\\frz?)([%-%d%.]+)"] = rotate end printmem("End of table insertion") local function linearmodo(currline) local one = aegisub.ms_from_frame(aegisub.frame_from_ms(currline.start_time)) local two = aegisub.ms_from_frame(aegisub.frame_from_ms(currline.start_time)+1) local red = currline.start_time local blue = currline.end_time local three = aegisub.ms_from_frame(aegisub.frame_from_ms(currline.end_time)-1) local four = aegisub.ms_from_frame(aegisub.frame_from_ms(currline.end_time)) local maths = math.floor(one-red+(two-one)/2) -- this voodoo magic gets the time length (in ms) from the start of the first subtitle frame to the actual start of the line time. local mathsanswer = math.floor(blue-red+three-blue+(four-three)/2) -- and this voodoo magic is the total length of the line plus the difference (which is negative) between the start of the last frame the line is on and the end time of the line. local posmatch, _ = "(\\pos)%(([%-%d%.]+,[%-%d%.]+)%)" if operations[posmatch] then currline.text = currline.text:gsub(posmatch,function(tag,val) local exes, whys = {}, {} for i,x in pairs({currline.rstartf,currline.rendf}) do local cx,cy = val:match("([%-%d%.]+),([%-%d%.]+)") mochaRatios(mocha,x) cx = (cx + mocha.diffx)*mocha.ratx + (1 - mocha.ratx)*mocha.currx cy = (cy + mocha.diffy)*mocha.raty + (1 - mocha.raty)*mocha.curry local r = math.sqrt((cx - mocha.currx)^2+(cy - mocha.curry)^2) cx = mocha.currx + r*dcos(currline.alpha + mocha.zrotd) cy = mocha.curry - r*dsin(currline.alpha + mocha.zrotd) table.insert(exes,round(cx,opts.posround)); table.insert(whys,round(cy,opts.posround)) end local s = ("\\move(%g,%g,%g,%g,%d,%d)"):format(exes[1],whys[1],exes[2],whys[2],maths,mathsanswer) aegisub.log(5,"%s\n",s) return s end) _,operations[posmatch] = operations[posmatch],nil end for pattern,func in pairs(operations) do -- iterate through the necessary operations if aegisub.progress.is_cancelled() then error("User cancelled") end currline.text = currline.text:gsub(pattern,function(tag,val) local values = {} for i,x in pairs({currline.rstartf,currline.rendf}) do mochaRatios(mocha,x) table.insert(values,func(val,currline,mocha,opts,tag)) end return ("%s%g\\t(%d,%d,1,%s%g)"):format(tag,values[1],maths,mathsanswer,tag,values[2]) end) end sub[currline.num] = currline operations[posmatch] = _ end local function nonlinearmodo(currline) for x = currline.rendf,currline.rstartf,-1 do -- new inner loop structure printmem("Inner loop") aegisub.log(5,"Round %d\n",x) aegisub.progress.title(string.format("Processing frame %g/%g",x,currline.rendf-currline.rstartf+1)) aegisub.progress.set((x-currline.rstartf)/(currline.rendf-currline.rstartf)*100) if aegisub.progress.is_cancelled() then error("User cancelled") end currline.start_time = aegisub.ms_from_frame(accd.startframe+x-1) currline.end_time = aegisub.ms_from_frame(accd.startframe+x) if not currline.is_comment then -- don't do any math for commented lines. currline.time_delta = currline.start_time - aegisub.ms_from_frame(accd.startframe) for vk,kv in ipairs(currline.trans) do if aegisub.progress.is_cancelled() then error("User cancelled") end currline.text = transformate(currline,kv) end mochaRatios(mocha,x) for pattern,func in pairs(operations) do -- iterate through the necessary operations if aegisub.progress.is_cancelled() then error("User cancelled") end currline.text = currline.text:gsub(pattern,function(tag,val) return tag..func(val,currline,mocha,opts,tag) end) end if clipa.clipme then currline.text = currline.text:gsub("\\i?clip%b()",function(a) return clippinate(currline,clipa,x) end,1) end currline.text = currline.text:gsub('\1',"") end sub.insert(currline.num+1,currline) currline.text = currline.orgtext end if global.delsourc then sub.delete(currline.num) end end local how2proceed = nonlinearmodo if opts.linear then how2proceed = linearmodo end for i,currline in ipairs(accd.lines) do printmem("Outer loop") currline.rstartf = currline.startframe - accd.startframe + 1 -- start frame of line relative to start frame of tracked data currline.rendf = currline.endframe - accd.startframe -- end frame of line relative to start frame of tracked data if opts.clip and currline.clip then clipa.clipme = true end currline.effect = "aa-mou"..currline.effect if opts.relative then if opts.stframe < 0 then mocha.start = currline.rendf + opts.stframe + 1 else mocha.start = currline.rstartf + opts.stframe - 1 end end if clipopts.relative and clipa.clipme then if tonumber(clipopts.stframe) < 0 then clipa.start = currline.rendf + clipopts.stframe + 1 else clipa.start = currline.rstartf + clipopts.stframe - 1 end end ensuretags(currline,opts,accd.styles,dim) currline.alpha = -datan(currline.ypos-mocha.ypos[mocha.start],currline.xpos-mocha.xpos[mocha.start]) if opts.origin then currline.beta = -datan(currline.oypos-mocha.ypos[mocha.start],currline.oxpos- mocha.xpos[mocha.start]) end currline.orgtext = currline.text -- tables are passed as references. how2proceed(currline) end for x = #sub,1,-1 do if tostring(sub[x].effect):match("^aa%-mou") then aegisub.log(5,"I choose you, %d!\n",x) table.insert(newlines,x) -- seems to work as intended end end return newlines -- yeah mang end function mochaRatios(mocha,x) mocha.ratx = mocha.xscl[x]/mocha.xscl[mocha.start] mocha.raty = mocha.yscl[x]/mocha.yscl[mocha.start] mocha.diffx = mocha.xpos[x]-mocha.xpos[mocha.start] mocha.diffy = mocha.ypos[x]-mocha.ypos[mocha.start] mocha.zrotd = mocha.zrot[x]-mocha.zrot[mocha.start] mocha.currx, mocha.curry = mocha.xpos[x], mocha.ypos[x] end function possify(pos,line,mocha,opts) local oxpos,oypos = pos:match("([%-%d%.]+),([%-%d%.]+)") local nxpos,nypos = makexypos(tonumber(oxpos),tonumber(oypos),mocha) local r = math.sqrt((nxpos - mocha.currx)^2+(nypos - mocha.curry)^2) nxpos = mocha.currx + r*dcos(line.alpha + mocha.zrotd) nypos = mocha.curry - r*dsin(line.alpha + mocha.zrotd) aegisub.log(5,"pos: (%f,%f) -> (%f,%f)\n",oxpos,oypos,nxpos,nypos) return ("(%g,%g)"):format(round(nxpos,opts.posround),round(nypos,opts.posround)) end function makexypos(xpos,ypos,mocha) local nxpos = (xpos + mocha.diffx)*mocha.ratx + (1 - mocha.ratx)*mocha.currx local nypos = (ypos + mocha.diffy)*mocha.raty + (1 - mocha.raty)*mocha.curry return nxpos,nypos end function orginate(opos,line,mocha,opts) local oxpos,oypos = opos:match("([%-%d%.]+),([%-%d%.]+)") local nxpos,nypos = makexypos(tonumber(oxpos),tonumber(oypos),mocha) aegisub.log(5,"org: (%f,%f) -> (%f,%f)\n",oxpos,oypos,nxpos,nypos) return ("(%g,%g)"):format(round(nxpos,opts.posround),round(nypos,opts.posround)) end function clippinate(line,clipa,iter) local cx, cy = clipa.xpos[iter], clipa.ypos[iter] local ratx, raty = clipa.xscl[iter]/clipa.xscl[clipa.start], clipa.yscl[iter]/clipa.yscl[clipa.start] local diffrz = clipa.zrot[iter] - clipa.zrot[clipa.start] aegisub.log(5,"cx: %f cy: %f\n",cx,cy) aegisub.log(5,"rx: %f ry: %f\n",ratx,raty) aegisub.log(5,"frz: %f\n",diffrz) local sclfac = 2^(line.sclip-1) local clip = "" local function xy(x,y) local xo,yo = x,y x = (tonumber(x) - clipa.xpos[clipa.start]*sclfac)*ratx y = (tonumber(y) - clipa.ypos[clipa.start]*sclfac)*raty local r = math.sqrt(x^2+y^2) local alpha = datan(y,x) x = cx*sclfac + r*dcos(alpha-diffrz) y = cy*sclfac + r*dsin(alpha-diffrz) aegisub.log(5,"Clip: %d %d -> %d %d\n",xo,yo,x,y) if line.rescaleclip then x = x*1024/sclfac y = y*1024/sclfac end return string.format("%d %d",round(x),round(y)) end clip = line.clip:gsub("([%.%d%-]+) ([%.%d%-]+)",xy) if line.rescaleclip then clip = string.format("\\%s(11,%s)",line.clips,clip) else clip = string.format("\\%s(%s)",line.clips,clip) end return clip end function transformate(line,trans) local t_s = trans[1] - line.time_delta local t_e = trans[2] - line.time_delta aegisub.log(5,"Transform: %d,%d -> %d,%d\n",trans[1],trans[2],t_s,t_e) return line.text:gsub("\\t%b()","\\"..sc(1)..string.format("t(%d,%d,%g,%s)",t_s,t_e,trans[3],trans[4]),1) end function scalify(scale,line,mocha,opts,tag) local newScale = scale*mocha.ratx -- sudden camelCase for no reason aegisub.log(5,"%s: %f -> %f\n",tag:sub(2),scale,newScale) return round(newScale,opts.sclround) end function rotate(rot,line,mocha,opts) local zrot = rot + mocha.zrotd aegisub.log(5,"frz: -> %f\n",zrot) return round(zrot,opts.rotround) end function munch(sub,sel) local changed = false for i,num in ipairs(sel) do if aegisub.progress.is_cancelled() then error("User cancelled") end local l1 = sub[num-1] local l2 = sub[num] if l1.text == l2.text and l1.effect == l2.effect then l1.end_time = l2.end_time debug("Munched line %d",num) sub[num-1]=l1 sub.delete(num) changed = true end end return changed end function cleanup(sub, sel, opts) -- make into its own macro eventually. opts = opts or {} local linediff local function cleantrans(cont) -- internal function because that's the only way to pass the line difference to it local t_s, t_e, ex, eff = cont:sub(2,-2):match("([%-%d]+),([%-%d]+),([%d%.]*),?(.+)") if tonumber(t_e) <= 0 then return string.format("%s",eff) end -- if the end time is less than or equal to zero, the transformation has finished. Replace it with only its contents. if tonumber(t_s) > linediff or tonumber(t_e) < tonumber(t_s) then return "" end -- if the start time is greater than the length of the line, the transform has not yet started, and can be removed from the line. if tonumber(ex) == 1 or ex == "" then return string.format("\\t(%s,%s,%s)",t_s,t_e,eff) end -- if the exponential factor is equal to 1 or isn't there, remove it (just makes it look cleaner) return string.format("\\t(%s,%s,%s,%s)",t_s,t_e,ex,eff) -- otherwise, return an untouched transform. end local ns = {} for i, v in ipairs(sel) do aegisub.progress.title(string.format("Castrating gerbils: %d/%d",i,#sel)) local lnum = sel[#sel-i+1] local line = sub[lnum] -- iterate backwards (makes line deletion sane) linediff = line.end_time - line.start_time line.text = line.text:gsub("}"..sc(6).."{","") -- merge sequential override blocks if they are marked as being the ones we wrote line.text = line.text:gsub(sc(6),"") -- remove superfluous marker characters for when there is no override block at the beginning of the original line line.text = line.text:gsub("\\t(%b())",cleantrans) -- clean up transformations (remove transformations that have completed) line.text = line.text:gsub("{}","") -- I think this is irrelevant. But whatever. for a in line.text:gmatch("{(.-)}") do aegisub.progress.set(math.random(100)) -- professional progress bars local transforms = {} line.text = line.text:gsub("\\(i?clip)%(1,m","\\%1(m") a = a:gsub("(\\t%b())", function(transform) aegisub.log(5,"Cleanup: %s found\n",transform) table.insert(transforms,transform) return sc(3) end) for k,v in pairs(alltags) do local _, num = a:gsub(v,"") --aegisub.log(5,"v: %s, num: %s, a: %s\n",v,num,a) a = a:gsub(v,"",num-1) end for i,trans in ipairs(transforms) do a = a:gsub(sc(3),trans,1) end line.text = line.text:gsub("{.-}",sc(1)..a..sc(2),1) -- I think... end line.text = line.text:gsub(sc(1),"{") line.text = line.text:gsub(sc(2),"}") line.effect = line.effect:gsub("aa%-mou","",1) sub[lnum] = line end if opts.sortd ~= "Default" then sel = dialog_sort(sub, sel, opts.sortd) end end function dialog_sort(sub, sel, sor) local function compare(a,b) if a.key == b.key then return a.num > b.num -- solve the disorganized sort problem. else return a.key > b.key end end -- local because why not? local sortF = ({ ['Time'] = function(l,n) return { key = l.start_time, num = n, data = l } end; ['Actor'] = function(l,n) return { key = l.actor, num = n, data = l } end; ['Effect'] = function(l,n) return { key = l.effect, num = n, data = l } end; ['Style'] = function(l,n) return { key = l.style, num = n, data = l } end; ['Layer'] = function(l,n) return { key = l.layer, num = n, data = l } end; })[sor] -- thanks, tophf local lines = {} for i,v in ipairs(sel) do if aegisub.progress.is_cancelled() then error("User cancelled") end -- should probably put these in every loop local line = sub[v] table.insert(lines,sortF(line,v)) end local strt = sel[1] -- not strictly necessary table.sort(lines, compare) for i = #sel,1,-1 do if aegisub.progress.is_cancelled() then error("User cancelled") end sub.delete(sel[i]) -- BALEET (in reverse because they are not necessarily contiguous) end sel = {} for i,v in ipairs(lines) do if aegisub.progress.is_cancelled() then error("User cancelled") end aegisub.progress.title(string.format("Sorting gerbils: %d/%d",i,#lines)) aegisub.progress.set(i/#lines*100) table.insert(sel,strt) sub.insert(strt,v.data) -- not sure this is the best place to do this but owell end return sel end function printmem(a) aegisub.log(5,"%s memory usage: %gkB\n",tostring(a),collectgarbage("count")) end function round(num, idp) -- borrowed from the lua-users wiki (all of the intelligent code you see in here is) local mult = 10^(idp or 0) return math.floor(num * mult + 0.5) / mult end function string:split(sep) -- borrowed from the lua-users wiki (single character split ONLY) local sep, fields = sep or ":", {} local pattern = string.format("([^%s]+)", sep) self:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end function table.tostring(t) if type(t) ~= 'table' then return tostring(t) else local s = '' local i = 1 while t[i] ~= nil do if #s ~= 0 then s = s..', ' end s = s..table.tostring(t[i]) i = i+1 end for k, v in pairs(t) do if type(k) ~= 'number' or k > i then if #s ~= 0 then s = s..', ' end local key = type(k) == 'string' and k or '['..table.tostring(k)..']' s = s..key..'='..table.tostring(v) end end return '{'..s..'}' end end function isvideo() -- a very rudimentary (but hopefully efficient) check to see if there is a video loaded. local l = aegisub.video_size() and true or false -- and forces boolean conversion? if l then return l else return l,"Validation failed: you don't have a video loaded." end end aegisub.register_macro("Motion Data - Apply", "Applies properly formatted motion tracking data to selected subtitles.", init_input, isvideo) function confmaker() local valtab = {} local conf = configscope() if not readconf(conf,{ ['main'] = gui.conf; ['clip'] = gui.clip; ['global'] = global }) then aegisub.log(0,"Config read failed!\n") end for key, value in pairs(global) do if gui.conf[key] then gui.conf[key].value = value end end gui.conf.enccom.value = encpre[global.encoder] or gui.conf.enccom.value local button, config = aegisub.dialog.display(gui.conf,{"Write","Write local","Clip...","Abort"}) local clipconf if button == "Clip..." then button, clipconf = aegisub.dialog.display(gui.clip,{"Write","Write local","Cancel","Abort"}) end if tostring(button):match("Write") then local clipconf = clipconf or {} if button == "Write local" then conf = dcp("?script/"..config_file) end if global.encoder ~= config.encoder then config.enccom = encpre[config.encoder] or config.enccom end for key,value in pairs(global) do global[key] = config[key] config[key] = nil end for i,field in ipairs(guiconf.clip) do if clipconf[field] == nil then clipconf[field] = gui.clip[field].value end end writeconf(conf,{ ['main'] = config; ['clip'] = clipconf; ['global'] = global }) elseif button == "Cancel" then confmaker() else aegisub.cancel() end end if config_file then aegisub.register_macro("Motion Data - Config", "Full config management.", confmaker) end gui.t = { vidlabel = { class = "label"; label = "The path to the loaded video"; x = 0; y = 0; height = 1; width = 30;}, input = { class = "textbox"; name = "input"; x = 0; y = 1; height = 1; width = 30;}, idxlabel = { class = "label"; label = "The path to the index file."; x = 0; y = 2; height = 1; width = 30;}, index = { class = "textbox"; name = "index"; x = 0; y = 3; height = 1; width = 30;}, sflabel = { class = "label"; label = "Start frame"; x = 0; y = 4; height = 1; width = 15;}, startf = { class = "intedit"; name = "startf"; x = 0; y = 5; height = 1; width = 15;}, eflabel = { class = "label"; label = "End frame"; x = 15; y = 4; height = 1; width = 15;}, endf = { class = "intedit"; name = "endf"; x = 15; y = 5; height = 1; width = 15;}, oplabel = { class = "label"; label = "Video file to be written"; x = 0; y = 6; height = 1; width = 30;}, output = { class = "textbox"; name = "output"; x = 0; y = 7; height = 1; width = 30;}, } function collecttrim(sub,sel,tokens) tokens.startt, tokens.endt = sub[sel[1]].start_time, sub[sel[1]].end_time for i,v in ipairs(sel) do local l = sub[v] local lst, let = l.start_time, l.end_time if lst < tokens.startt then tokens.startt = lst end if let > tokens.endt then tokens.endt = let end end tokens.startf, tokens.endf = aegisub.frame_from_ms(tokens.startt), aegisub.frame_from_ms(tokens.endt)-1 tokens.lenf = tokens.endf-tokens.startf+1 tokens.lent = tokens.endt-tokens.startt end -- #{encbin} #{input} #{prefix} #{index} #{output} #{startf} #{lenf} #{endf} #{startt} #{lent} #{endt} #{nl} function getvideoname(sub) for x = 1,#sub do if sub[x].key == "Video File" then return sub[x].value:gsub("^ ","") end end windowerr(false,"Could not find 'Video File'. Try saving your script before rerunning the macro.") end function trimnthings(sub,sel) local conf = configscope() if conf then if not readconf(conf,{ ['global'] = global }) then aegisub.log(0,"Failed to read config!\n") end end local tokens = {} tokens.encbin = global.encbin tokens.prefix = dcp(global.prefix) tokens.nl = "\n" collecttrim(sub,sel,tokens) tokens.input = getvideoname(sub):gsub("[A-Z]:\\",""):gsub(".+[^\\/]-[\\/]","") assert(not tokens.input:match("?dummy"), "No dummy videos allowed. Sorry.") tokens.inpath = dcp("?video/") tokens.index = tokens.input:match("(.+)%.[^%.]+$") tokens.output = tokens.index -- huh. if not global.gui_trim then writeandencode(tokens) else someguiorsmth(tokens) end end function someguiorsmth(tokens) gui.t.input.value = tokens.input gui.t.index.value = tokens.index gui.t.startf.value = tokens.startf gui.t.endf.value = tokens.endf gui.t.output.value = tokens.output local button, opts = aegisub.dialog.display(gui.t) if button then for k,v in pairs(opts) do tokens[k] = v end tokens.startt, tokens.endt = aegisub.ms_from_frame(tokens.startf), aegisub.ms_from_frame(tokens.endf) tokens.lenf, tokens.lent = tokens.endf-tokens.startf, tokens.endt-tokens.startt writeandencode(tokens) end end function writeandencode(tokens) tokens.startt, tokens.endt, tokens.lent = tokens.startt/1000, tokens.endt/1000, tokens.lent/1000 local function ReplaceTokens(token) return tokens[token:sub(2,-2)] end local winpaths = tostring(winpaths) local encsh = tokens.prefix.."encode"..({['false'] = '.sh', ['true'] = '.bat'})[winpaths] local sh = io.open(encsh,"w+") assert(sh,"Encoding command could not be written. Check your prefix.") -- to solve the 250 byte limit, we write to a self-deleting batch file. sh:write(global.enccom:gsub("#(%b{})",ReplaceTokens)..({['false'] = '\nrm "$0"', ['true'] = '\ndel "%0"'})[winpaths]) sh:close() if os.execute((({['false'] = 'sh "%s"', ['true'] = '""%s""'})[winpaths]):format(encsh)) ~= 0 then error("Encoding failed!") end end function debug(...) if dbg then aegisub.log(0,...) end end aegisub.register_macro("Motion Data - Trim","Cuts and encodes the current scene for use with motion tracking software.", trimnthings, isvideo) local tr = aegisub.gettext script_name = tr('Duplicate and shift by 1 Frame backwards') script_description = tr('Duplicte selected lines and shift them to the frame before the original start frame') script_author = 'Thomas Goyne' script_version = '1' local contiguous_chunks contiguous_chunks = function(arr) local ret = { { } } local last = ret[1] local _list_0 = arr for _index_0 = 1, #_list_0 do local value = _list_0[_index_0] if not last[#last] or last[#last] + 1 == value then last[#last + 1] = value else last = { value } ret[#ret + 1] = last end end return ret end return aegisub.register_macro(script_name, script_description, function(subs, selection, active) local new_lines = { } local _list_0 = contiguous_chunks(selection) for _index_0 = 1, #_list_0 do local _continue_0 = false repeat local chunk = _list_0[_index_0] if not (#chunk > 0) then _continue_0 = true break end local start = chunk[1] local _list_1 = chunk for _index_1 = 1, #_list_1 do local sel = _list_1[_index_1] local line = subs[sel] local frame = aegisub.frame_from_ms(line.start_time) line.start_time = aegisub.ms_from_frame(frame - 1) line.end_time = aegisub.ms_from_frame(frame) if not new_lines[start] then new_lines[start] = { } end table.insert(new_lines[start], line) end _continue_0 = true until true if not _continue_0 then break end end local offset = 0 local new_selection = { } for line, chunk in pairs(new_lines) do subs.insert(line + offset, unpack(chunk)) for i = 1, #chunk do new_selection[#new_selection + 1] = line + offset + i - 1 end offset = offset + #chunk end aegisub.set_undo_point(tr('duplicate lines')) return new_selection, new_selection[1] end) --Script Usage: Make a Bezier curve in aeigsub using the clip drawing function. --The Curve can not consist of more than one line. --{\k0} must be added before every letter, or the script will not work. --For example: "Hello World" has to be "{\k0}H{\k0}e{\k0}l{\k0}l{\k0}o {\k0}W{\k0}o{\k0}r{\k0}l{\k0}d" --Example Line: "{\clip(m 0 300 b 0 0 300 0 300 300)}{\k0}I{\k0}t {\k0}r{\k0}e{\k0}a{\k0}l{\k0}l{\k0}y {\k0}w{\k0}o{\k0}r{\k0}k{\k0}s{\k0}!" include("karaskel.lua") script_name = "Bezier Curve Imposition" script_description = "An effect to impose text onto a Bezier curve" script_author = "an eer?" script_version = "0.92"

function Bezier_impose(subs,sel) aegisub.progress.task("Getting header data...") local meta, styles = karaskel.collect_head(subs)

aegisub.progress.task("Applying effect...") local i, ai, maxi, maxai = 1, 1, #sel, #sel while i <= maxi do aegisub.progress.task(string.format("Applying effect (%d/%d)...", ai, maxai)) aegisub.progress.set((ai-1)/maxai*100) local l = subs[sel[i]] if l.class == "dialogue" and not l.comment then karaskel.preproc_line(subs, meta, styles, l) do_fx(subs, meta, l) maxi = maxi - 1 subs.delete(sel[i]) else i = i + 1 end ai = ai + 1 end aegisub.progress.task("Finished!") aegisub.progress.set(100) end

line_n,t,p = 0,{},{} function do_fx(subs, meta, line) for i = 1, line.kara.n do local syl = line.kara[i] local x=syl.center local y=line.margin_v + 40 if i == 1 then syl_n,arc,g,u =0,0,1,1 for v in line.text:gmatch("%d+") do --Gets the Bezier points from the script. p[u] = v u = u + 1 end end

--dt is the small steps from one point to another, must be a small number. dt = .0001;

--Define vars. n,w,x_c,y_c = 0,1,{},{}

--Creates a set of t to evaluate, 0 to 1 in steps of dt. for w = 1,1/dt do t[w]=dt*w end

--Finds the points of the Bezier curve for ever t. for ii = 1,#(t) do x_c[ii] = (1-t[ii])^3*p[1] + 3*t[ii]*p[3]*(1-t[ii])^2+3*t[ii]^2*p[5]*(1-t[ii])+p[7]*t[ii]^3; y_c[ii] = (1-t[ii])^3*p[2] + 3*t[ii]*p[4]*(1-t[ii])^2+3*t[ii]^2*p[6]*(1-t[ii])+p[8]*t[ii]^3; end

--sums the arc lenght of the Bezier curve untill it reaches a letter. while n == 0 do g = g + 1 arc = ((x_c[g]-x_c[g-1])^2+(y_c[g]-y_c[g-1])^2)^.5 + arc if x < arc then n = 1 end end

--The derivative of the Bezier cruve, used to find the slope of the Bezier curve. dx = 3*(-t[g]^2+2*t[g]-1)*p[1]+3*(3*t[g]^2-4*t[g]+1)*p[3]+3*(-3*t[g]^2+2*t[g])*p[5]+3*t[g]^2*p[7] dy = 3*(-t[g]^2+2*t[g]-1)*p[2]+3*(3*t[g]^2-4*t[g]+1)*p[4]+3*(-3*t[g]^2+2*t[g])*p[6]+3*t[g]^2*p[8]

--Finds the angle of the slope. if dx < 0 then theta = math.pi+math.atan(dy/dx) else theta = math.atan(dy/dx) end

--Creates lines. l = table.copy(line) l.text = string.format("{\\pos(%d,%d)\\frz%d}{%d}%s", x_c[g], y_c[g], -theta*(180/math.pi), #p, syl.text_stripped) l.start_time=line.start_time l.end_time=line.end_time l.layer = 1 subs.append(l) line_n=line_n+1 syl_n=syl_n+(1000/line.kara.n) end end aegisub.register_macro("Bezier", "Impose test onto a Bezier curve", Bezier_impose) include("utils.lua") script_name = "BT.601 -> BT.709 Color Fixer" script_description = "Change colors from BT.601 to BT.709." script_name_2 = "BT.601 -> BT.709 Global Color Fixer" script_description_2 = "Globally change colors from BT.601 to BT.709." script_author = "Daiz" script_version = "1.0.1" local Rec601 = { Kr = 0.299, Kg = 0.587, Kb = 0.114 } local Rec709 = { Kr = 0.2126, Kg = 0.7152, Kb = 0.0722 } function RGBtoYUV(r, g, b, matrix) local Kr = matrix.Kr local Kg = matrix.Kg local Kb = matrix.Kb local y = (Kr*219/255)*r + (Kg*219/255)*g + (Kb*219/255)*b local v = 112/255*r - Kg*112/255*g/(1-Kr) - Kb*112/255*b/(1-Kr) local u = - Kr*112/255*r/(1-Kb) - Kg*112/255*g/(1-Kb) + 112/255*b

return y+16, u+128, v+128 end function YUVtoRGB(y, u, v, matrix) local Kr = matrix.Kr local Kg = matrix.Kg local Kb = matrix.Kb

local r = (255/219)*y + (255/112)*v*(1-Kr) - (255*16/219 + 255*128/112*(1-Kr)) local g = (255/219)*y - (255/112)*u*(1-Kb)*Kb/Kg - (255/112)*v*(1-Kr)*Kr/Kg - (255*16/219 - 255/112*128*(1- Kb)*Kb/Kg - 255/112*128*(1-Kr)*Kr/Kg) local b = (255/219)*y + (255/112)*u*(1-Kb) - (255*16/219 + 255*128/112*(1-Kb))

r = clamp(math.floor(r + 0.5), 0, 255) g = clamp(math.floor(g + 0.5), 0, 255) b = clamp(math.floor(b + 0.5), 0, 255)

return r, g, b end function cm_conv(r, g, b, m1, m2) local y, u, v = RGBtoYUV(r, g, b, m1) local r, g, b = YUVtoRGB(y, u, v, m2) return r, g, b end function replace(colorstring) local r, g, b, a = extract_color(colorstring) nr, ng, nb = cm_conv(r, g, b, Rec601, Rec709) return ass_color(nr, ng, nb) end function correct(subs, sel, active)

for _, i in pairs(sel) do

local l = subs[i] local t = l.text l.text = t:gsub("&H%x%x%x%x%x%x&", replace) subs[i] = l

end

aegisub.set_undo_point("BT.601->BT.709 Color Fix") end function correct_global(subs, sel, active)

for i = 1, #subs do local l = subs[i]

if l.class == "info" then if l.key == "YCbCr Matrix" then if l.value == "TV.601" then l.value = "TV.709" end end end

if l.class == "style" then

local r, g, b, a = extract_color(l.color1) local nr, ng, nb = cm_conv(r, g, b, Rec601, Rec709) l.color1 = ass_style_color(nr, ng, nb, a)

r, g, b, a = extract_color(l.color2) nr, ng, nb = cm_conv(r, g, b, Rec601, Rec709) l.color2 = ass_style_color(nr, ng, nb, a)

r, g, b, a = extract_color(l.color3) nr, ng, nb = cm_conv(r, g, b, Rec601, Rec709) l.color3 = ass_style_color(nr, ng, nb, a)

r, g, b, a = extract_color(l.color4) nr, ng, nb = cm_conv(r, g, b, Rec601, Rec709) l.color4 = ass_style_color(nr, ng, nb, a) end if l.class == "dialogue" then local t = l.text l.text = t:gsub("&H%x%x%x%x%x%x&", replace) end

subs[i] = l

end

aegisub.set_undo_point("BT.601->BT.709 Global Color Fix") end aegisub.register_macro( script_name, script_description, correct ) aegisub.register_macro( script_name_2, script_description_2, correct_global ) include("utils.lua") script_name = "BT.709 -> BT.601 Color Converter" script_description = "Change colors from BT.709 to BT.601." script_name_2 = "BT.709 -> BT.601 Global Color Converter" script_description_2 = "Globally change colors from BT.709 to BT.601." script_author = "Daiz" script_version = "1.0.0" local Rec601 = { Kr = 0.299, Kg = 0.587, Kb = 0.114 } local Rec709 = { Kr = 0.2126, Kg = 0.7152, Kb = 0.0722 } function RGBtoYUV(r, g, b, matrix) local Kr = matrix.Kr local Kg = matrix.Kg local Kb = matrix.Kb local y = (Kr*219/255)*r + (Kg*219/255)*g + (Kb*219/255)*b local v = 112/255*r - Kg*112/255*g/(1-Kr) - Kb*112/255*b/(1-Kr) local u = - Kr*112/255*r/(1-Kb) - Kg*112/255*g/(1-Kb) + 112/255*b

return y+16, u+128, v+128 end function YUVtoRGB(y, u, v, matrix) local Kr = matrix.Kr local Kg = matrix.Kg local Kb = matrix.Kb

local r = (255/219)*y + (255/112)*v*(1-Kr) - (255*16/219 + 255*128/112*(1-Kr)) local g = (255/219)*y - (255/112)*u*(1-Kb)*Kb/Kg - (255/112)*v*(1-Kr)*Kr/Kg - (255*16/219 - 255/112*128*(1- Kb)*Kb/Kg - 255/112*128*(1-Kr)*Kr/Kg) local b = (255/219)*y + (255/112)*u*(1-Kb) - (255*16/219 + 255*128/112*(1-Kb))

r = clamp(math.floor(r + 0.5), 0, 255) g = clamp(math.floor(g + 0.5), 0, 255) b = clamp(math.floor(b + 0.5), 0, 255)

return r, g, b end function cm_conv(r, g, b, m1, m2) local y, u, v = RGBtoYUV(r, g, b, m1) local r, g, b = YUVtoRGB(y, u, v, m2) return r, g, b end function replace(colorstring) local r, g, b, a = extract_color(colorstring) nr, ng, nb = cm_conv(r, g, b, Rec709, Rec601) return ass_color(nr, ng, nb) end function correct(subs, sel, active)

for _, i in pairs(sel) do

local l = subs[i] local t = l.text l.text = t:gsub("&H%x%x%x%x%x%x&", replace) subs[i] = l

end

aegisub.set_undo_point("BT.709->BT.601 Color Convert") end function correct_global(subs, sel, active)

for i = 1, #subs do local l = subs[i]

if l.class == "info" then if l.key == "YCbCr Matrix" then if l.value == "TV.709" then l.value = "TV.601" end end end

if l.class == "style" then

local r, g, b, a = extract_color(l.color1) local nr, ng, nb = cm_conv(r, g, b, Rec709, Rec601) l.color1 = ass_style_color(nr, ng, nb, a)

r, g, b, a = extract_color(l.color2) nr, ng, nb = cm_conv(r, g, b, Rec709, Rec601) l.color2 = ass_style_color(nr, ng, nb, a)

r, g, b, a = extract_color(l.color3) nr, ng, nb = cm_conv(r, g, b, Rec709, Rec601) l.color3 = ass_style_color(nr, ng, nb, a)

r, g, b, a = extract_color(l.color4) nr, ng, nb = cm_conv(r, g, b, Rec709, Rec601) l.color4 = ass_style_color(nr, ng, nb, a) end if l.class == "dialogue" then local t = l.text l.text = t:gsub("&H%x%x%x%x%x%x&", replace) end

subs[i] = l

end

aegisub.set_undo_point("BT.709->BT.601 Global Color Convert") end aegisub.register_macro( script_name, script_description, correct ) aegisub.register_macro( script_name_2, script_description_2, correct_global ) --[[ INSTRUCTIONS For regular fade, type only 'fade in' / 'fade out' values. Checking alpha will use alpha transform instead, with the fade in/out values and accel. Checking colours will do colour transforms (with accel). If only one checked, the other will be alpha transform. Checking blur will do a blur transform with given start and end blur (and accel), using the current blur as the middle value. In case of user stupidity, ie. blur missing, 0.6 is used as default. For letter by letter, the dropdown is for each letter, while fade in/out are for the overall fades. Letter by letter using \ko - uses {\ko#} tags instead of transforms for fade in. if the value is under 40, it's used as \ko[value]; if it's 40+, it's considered to be the overall fade, ie. when the last letter appears. \ko by word fades in by word instead of by letter. (Inline tags are supported, but if you fade by word and have tags in the middle of a word, it won't work as you want it to.) Fade across multiple lines will create a set of alpha transforms across lines. Nukes all present alpha tags; supports shadow alpha. "Global time" will use times relative to video, rather than of each individual line. Fade in to current frame - sets fade in to current video frame. Fade out from current frame - sets fade out to current video frame. Extra functions: Fade between 0 and 1 gives you that fraction of the line's duration, so fade in 0.2 with 1 second is \fad(200,0). Negative fade gives you the inverse with respect to duration, so if dur=3000 and fade in is -500, you get \fad(2500,0), or to 500 from end.

Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#fade ]] script_name="Apply Fade" script_description="Applies fade to selected lines" script_author="unanimated" script_version="3.92" script_namespace="ua.ApplyFade" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.9.2" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' function def() if fadin<=1 and fadin>0 then fadin=round(dur*fadin) end if fadout<=1 and fadout>0 then fadout=round(dur*fadout) end if fadin<0 then fadin=dur+fadin end if fadout<0 then fadout=dur+fadout end if fadin<0 then fadin=0 end if fadout<0 then fadout=0 end end function fade(subs,sel) for z,i in ipairs(sel) do progress("Processing line: "..z.."/"..#sel) line=subs[i] text=line.text dur=line.end_time-line.start_time def()

-- remove existing fade text=text:gsub("\\fad%([%d%.%,]-%)","")

-- standard fade if P=="Apply Fade" then text="{\\fad("..fadin..","..fadout..")}"..text text=text:gsub("%)}{\\",")\\") :gsub("{}","") end

-- letter by letter if P=="Letter by Letter" then

-- delete old letter-by-letter if present if text:match("\\t%([%d,]+\\alpha[^%(%)]+%)}%S$") then text=text:gsub("\\t%([^%(%)]-%)","") :gsub("\\alpha&H%x+&","") :gsub("{}","") end

if not res.del then

-- fail if letter fade is larger than total fade lf=tonumber(res.letterfade) if fadin>0 and fadin<=lf or fadout>0 and fadout<=lf then ADD({{class="label", label="The fade for each letter must be smaller than overall fade."}},{"Fuck! Sorry, I'm stupid. Won't happen again."}) ak() end

-- mode: in, out, both if fadout==0 then mode=1 elseif fadin==0 then mode=2 else mode=3 end

-- save initial tags; remove other tags/comments tags=text:match("^({\\[^}]*})") or "" orig=text:gsub("^({\\[^}]*})","") :gsub("{[^\\}]-}","") text=text:gsub("%b{}","") :gsub("%s*$","") :gsub("\\N","*")

-- letter-by-letter fade happens here outfade=dur-fadout count=0 text3="" al=tags:match("^{[^}]-\\alpha&H(%x%x)&") or "00"

matches=re.find(text,"[\\w[:punct:]][\\s\\\\*]*") length=#matches ftime1=((fadin-lf)/(length-1)) ftime2=((fadout-lf)/(length-1)) for _,match in ipairs(matches) do ch=match.str if res.rtl then fin1=math.floor(ftime1*(#matches-count-1)) else fin1=math.floor(ftime1*count) end fin2=fin1+lf if res.rtl then fout1=math.floor(ftime2*(#matches-count-1)+outfade) else fout1=math.floor(ftime2*count+outfade) end fout2=fout1+lf if mode==1 then text2m="{\\alpha&HFF&\\t("..fin1..","..fin2..",\\alpha&H"..al.."&)}"..ch end if mode==2 then text2m="{\\alpha&H"..al.."&\\t("..fout1..","..fout2..",\\alpha&HFF&)}"..ch end if mode==3 then text2m="{\\alpha&HFF&\\t("..fin1..","..fin2..",\\alpha&H"..al.."&)\\t("..fout1..","..fout2..",\\alpha&HFF&)}"..ch end text3=text3..text2m count=count+1 end

-- join saved tags + new text with transforms text=tags..text3 text=text:gsub("}{","") :gsub("%*","\\N") if orig:match("{\\") then text=textmod(orig) end end end

text=text:gsub("\\fad%(0,0%)","") :gsub("{}","") line.text=text subs[i]=line end end function fadalpha(subs,sel) if res.clr or res.crl then res.alf=true end blin="\\blur"..res.bli blout="\\blur"..res.blu if res.vin or res.vout then vfcheck() vt=math.floor((fr2ms(vframe+1)+fr2ms(vframe))/2) end for z,i in ipairs(sel) do progress("Processing line: "..z.."/"..#sel) line=subs[i] text=line.text ortext=text sr=stylechk(subs,line.style) st,et,dur=times() def() if res.vin then fadin=vt-st end if res.vout then fadout=et-vt end

if not text:match("^{\\[^}]-}") then text="{\\arfa}"..text end

col1=res.c1:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col2=res.c2:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&")

text=text:gsub("\\1c","\\c") notra=text:gsub("\\t%b()","") primary=notra:match("^{[^}]-\\c(&H%x+&)") or sr.color1:gsub("H%x%x","H") outline=notra:match("^{[^}]-\\3c(&H%x+&)") or sr.color3:gsub("H%x%x","H") shadcol=notra:match("^{[^}]-\\4c(&H%x+&)") or sr.color4:gsub("H%x%x","H") primary2=text:match("^{[^}]*\\c(&H%x+&)[^}]-}") or sr.color1:gsub("H%x%x","H") outline2=text:match("^{[^}]*\\3c(&H%x+&)[^}]-}") or sr.color3:gsub("H%x%x","H") shadcol2=text:match("^{[^}]*\\4c(&H%x+&)[^}]-}") or sr.color4:gsub("H%x%x","H") border=tonumber(text:match("^{[^}]-\\bord([%d%.]+)")) or sr.outline shadow=tonumber(text:match("^{[^}]-\\shad([%d%.]+)")) or sr.shadow kolora1="\\c"..col1 kolora3="\\3c"..col1 kolora4="\\4c"..col1 kolora,see1,see3,see4="","","","" kolorb1="\\c"..col2 kolorb3="\\3c"..col2 kolorb4="\\4c"..col2 kolorb="" if col1~=primary then kolora=kolora..kolora1 see1="\\c"..primary end if col1~=outline then kolora=kolora..kolora3 see3="\\3c"..outline end if col1~=shadcol then kolora=kolora..kolora4 see4="\\4c"..shadcol end if col2~=primary2 then kolorb=kolorb..kolorb1 end if col2~=outline2 then kolorb=kolorb..kolorb3 end if col2~=shadcol2 then kolorb=kolorb..kolorb4 end a00="\\alpha&H00&" aff="\\alpha&HFF&" lb=""

-- blur w/o alpha if res.blur then lineblur=text:match("^{[^}]-(\\blur[%d%.]+)") if lineblur==nil then lineblur="\\blur0.6" end text=text:gsub("^({[^}]-)\\blur[%d%.]+","%1") text=text:gsub("^{}","{\\arfa}") if not text:match("^{\\") then text="{\\notarealtag}"..text end if fadin==0 then lb=lineblur else lb="" end if not res.alf then if fadin~=0 then text=text:gsub("^({\\[^}]-)}","%1"..blin.."\\t(0,"..fadin..","..res.inn..","..lineblur..")}") end if fadout~=0 then text=text:gsub("^({\\[^}]-)}","%1"..lb.."\\t("..dur-fadout..",0,"..res.utt..","..blout..")}") end text=text:gsub("\\notarealtag","") end end if not res.blur then lineblur="" blin="" blout="" end if res.alf then if fadin~=0 then -- fade from colour if res.crl then if kolora~="" then text=text:gsub("^{\\[^}]-}",function(a) if a:match("\\t") then nt="" for n,t in a:gmatch("(.-)(\\t%b())") do nt=nt..n:gsub("\\[34]?c&H%x+&","")..t end nt=nt..a:match(".*\\t%b()(.-)$"):gsub("\\[34]?c&H%x+&","") return nt else return a:gsub("\\[34]?c&H%x+&","") end end) text=text:gsub("^{}","{\\arfa}") tfc=kolora..blin.."\\t(0,"..fadin..","..res.inn..","..see1..see3..see4..lineblur..")" text=text:gsub("^({\\[^}]-)}",function(a) if a:match("\\t") then return a:gsub("^(.-)(\\t.*)","%1"..tfc.."%2}") else return a..tfc.."}" end end) end -- inline colour tags for t in text:gmatch(".({\\[^}]-})") do det=t:gsub("\\t%b()","") if det:match("\\[13]?c") then col1=det:match("(\\c&H%x+&)") or "" if col1==kolora1 then col1="" end col3=det:match("(\\3c&H%x+&)") or "" if col3==kolora3 then col3="" end col4=det:match("(\\4c&H%x+&)") or "" if col4==kolora4 then col4="" end if (col1..col3..col4):len()>0 then tfic="\\t(0,"..fadin..","..res.inn..","..col1..col3..col4..")" if t:match("\\t") then t2="" for n,tf in t:gmatch("(.-)(\\t%b())") do t2=t2..n:gsub("\\c&H%x+&",kolora1):gsub("\\3c&H%x+&",kolora3):gsub("\\4c&H%x+&",kolora4)..tf end t2=t2..t:match(".*\\t%b()(.- )$"):gsub("\\c&H%x+&",kolora1):gsub("\\3c&H%x+&",kolora3):gsub("\\4c&H%x+&",kolora4) t2=t2:gsub("^(.-)(\\t.*)","%1"..tfic.."%2") else t2=t:gsub("\\c&H%x+&",kolora1):gsub("\\3c&H%x+&",kolora3):gsub("\\4c&H%x+&",kolora4) t2=t2:gsub("({[^}]-)}","%1"..tfic.."}") end text=text:gsub(esc(t),t2) end end end else -- fade from alpha subalf=false st_alf=notra:match("^{[^}]-(\\alpha&H%x%x&)") st_a1=notra:match("^{[^}]-(\\1a&H%x%x&)") st_a3=notra:match("^{[^}]-(\\3a&H%x%x&)") st_a4=notra:match("^{[^}]-(\\4a&H%x%x&)") if st_alf==nil then toalf=a00 else toalf=st_alf end tosub=toalf:match("&H%x%x&") if st_a1==nil then toa1="\\1a"..tosub else subalf=true toa1=st_a1 end if st_a3==nil then toa3="\\3a"..tosub else subalf=true toa3=st_a3 end if st_a4==nil then toa4="\\4a"..tosub else subalf=true toa4=st_a4 end if subalf then toalf=toa1..toa3..toa4 else toalf=toalf end fromalf=toalf:gsub("&H%x%x&","&HFF&") text=text:gsub("^{\\[^}]-}",function(a) if a:match("\\t") then nt="" for n,t in a:gmatch("(.-)(\\t%b())") do nt=nt..n:gsub("\\%w+a&H%x+&","")..t end nt=nt..a:match(".*\\t%b()(.-)$"):gsub("\\%w+a&H%x+&","") return nt else return a:gsub("\\%w+a&H%x+&","") end end) tfa=blin..fromalf.."\\t(0,"..fadin..","..res.inn..","..lineblur..toalf..")" text=text:gsub("^({\\[^}]-)}",function(a) if a:match("\\t") then return a:gsub("^(.-)(\\t.*)","%1"..tfa.."%2}") else return a..tfa.."}" end end) -- inline alpha tags for t in text:gmatch(".({\\[^}]-})") do det=t:gsub("\\t%b()","") arfa=det:match("(\\alpha&H%x+&)") or "" arf1=det:match("(\\1a&H%x+&)") or "" arf3=det:match("(\\3a&H%x+&)") or "" arf4=det:match("(\\4a&H%x+&)") or "" toarfa=arfa..arf1..arf3..arf4 if toarfa~="" then fromarfa=toarfa:gsub("&H%x%x&","&HFF&") tfia=fromarfa.."\\t(0,"..fadin..","..res.inn..","..toarfa..")" if t:match("\\t") then t2="" for n,tf in t:gmatch("(.-)(\\t%b())") do t2=t2..n:gsub("\\alpha&H%x+&","") :gsub("\\[134]a&H%x+&","")..tf end t2=t2..t:match(".*\\t%b()(.-)$"):gsub("\\alpha&H%x+&","") :gsub("\\[134]a&H%x+&","") t2=t2:gsub("^(.-)(\\t.*)","%1"..tfia.."%2") else t2=t:gsub("\\alpha&H%x+&","") :gsub("\\[134]a&H%x+&","") t2=t2:gsub("({[^}]-)}","%1"..tfia.."}") end text=text:gsub(esc(t),t2) end end end end

if fadout~=0 then -- fade to colour if res.clr then if kolorb~="" then text=text:gsub("^({\\[^}]-)}","%1"..lb.."\\t("..dur-fadout..",0,"..res.utt..","..kolorb..blout..")}") end -- inline colour tags for t in text:gmatch(".({\\[^}]-})") do if t:match("\\[13]?c") or t:match("\\alpha") then k_out="" if t:match("\\c&H%x+&")~=kolorb1 and t:match("\\c&H%x+&")~=nil then k_out=k_out..kolorb1 end if t:match("\\3c&H%x+&")~=kolorb3 and t:match("\\3c&H%x+&")~=nil then k_out=k_out..kolorb3 end if t:match("\\4c&H%x+&")~=kolorb4 and t:match("\\4c&H%x+&")~=nil then k_out=k_out..kolorb4 end t2=t:gsub("({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.utt..","..k_out..")}") text=text:gsub(esc(t),t2) end end -- fade to alpha else if text:match("^{[^}]-(\\[134]a&H%x%x&)") then toarf="\\1a&HFF&".."\\3a&HFF&".."\\4a&HFF&" else toarf=aff end text=text:gsub("^({\\[^}]-)}","%1"..lb.."\\t("..dur-fadout..",0,"..res.utt..","..blout..toarf..")}") -- inline alpha tags for t in text:gmatch(".({\\[^}]-})") do toarf="" if t:match("\\alpha") then toarf=toarf..aff end if t:match("\\1a") then toarf=toarf.."\\1a&HFF&" end if t:match("\\3a") then toarf=toarf.."\\3a&HFF&" end if t:match("\\4a") then toarf=toarf.."\\4a&HFF&" end if toarf~="" then t2=t:gsub("({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.utt..","..toarf..")}") text=text:gsub(esc(t),t2) end end end end if border==0 then text=text:gsub("\\3c&H%x+&","") end if shadow==0 then text=text:gsub("\\4c&H%x+&","") end if not text:match("\\fad%(0,0%)") then text=text:gsub("\\fad%(%d+,%d+%)","") end -- nuke fade if res.keepthefade then if res.crl then f1=fadin else f1=0 end if res.clr then f2=fadout else f2=0 end if f1+f2>0 then text=text:gsub("^{\\","{\\fad("..f1..","..f2..")\\") end end text=text:gsub("\\t%([^\\]+%)","") end text=text:gsub("\\arfa","") line.text=text subs[i]=line end end function koko_da(subs,sel) if fadin<1 then t_error("Fade in must be at least 1",true) end for x,i in ipairs(sel) do progress("Processing line: "..x.."/"..#sel) line=subs[i] text=line.text text=text:gsub("\\ko%d+","") :gsub("{}","")

-- save initial tags; remove other tags/comments tags=text:match("^{\\[^}]-}") or "" orig=text:gsub("^({\\[^}]*})","") text=text:gsub("{[^}]*}","") :gsub("%s*$","") :gsub("\\N","*")

--letter if not res.word then matches=re.find(text,"[\\w[:punct:]][\\s\\\\*]*") len=#matches if fadin>=40 then ko=round(fadin/(len-1))/10 else ko=fadin end text=re.sub(text,"([\\w[:punct:]])","{\\\\ko"..ko.."}\\1") else --word matches=re.find(text,"[\\w[:punct:]]+[\\s\\\\*]*") len=#matches if fadin>=40 then ko=round(fadin/(len-1)/10) else ko=fadin end text=re.sub(text,"([\\w[:punct:]]+)","{\\\\ko"..ko.."}\\1") end

-- join saved tags + new text with transforms text=tags..text if not text:match("\\2a&HFF&") then text=text:gsub("^{","{\\2a&HFF&") end text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("%*","\\N") if orig:match("{\\") then text=textmod(orig) end line.text=text subs[i]=line end end function fadeacross(subs,sel) if fadin<0 then fadin=0 end if fadout<0 then fadout=0 end full=0 war=0 S=subs[sel[1]].start_time E=subs[sel[#sel]].end_time -- get total duration for z,i in ipairs(sel) do line=subs[i] dur=line.end_time-line.start_time full=full+dur if line.start_timeE then E=line.end_time war=1 end end -- Error if fades too long if res.time and fadin+fadout>E-S or not res.time and fadin+fadout>full then t_error("Error. Fades are longer than the duration of lines",true) end -- Warning if not sorted by time if war==1 then t_error("Not sorted by time. \nDeal with the consequences.") end -- Fade full2=E-S if res.time then full=full2 end durs=0 durs1=0 for z,i in ipairs(sel) do progress("Processing line: "..z.."/"..#sel) line=subs[i] text=line.text start,endt,dur=times() startf=ms2fr(start) endf=ms2fr(endt) if endf-startf==1 then oneframe=true else oneframe=false end -- check shadow alpha sr=stylechk(subs,line.style) shad=sr.color4:match("H(%x%x)") if text:match("\\4a") then shad=text:match("\\4a&H(%x%x)") end if shad~="00" then shade=1-(tonumber(shad,16)/256) end kill=1 -- fade in if dursfadin then in_end=dur-(durs-fadin) tim="0,"..in_end.."," kill=0 else tim="" end alfin_s=tohex(255-(round((durs-dur)/fadin*255))) alfin_e=tohex(255-(round(durs/fadin*255))) alfin_1=tohex(255-(round((durs-dur/2)/fadin*255))) if shad~="00" then shin_s=tohex(255-round((255-tonumber(alfin_s,16))*shade)) shin_e=tohex(255-round((255-tonumber(alfin_e,16))*shade)) shin_1=tohex(255-round((255-tonumber(alfin_1,16))*shade)) instart="\\1a&H"..alfin_s.."&\\3a&H"..alfin_s.."&\\4a&H"..shin_s.."&" inend="\\1a&H"..alfin_e.."&\\3a&H"..alfin_e.."&\\4a&H"..shin_e.."&" onefadin="\\1a&H"..alfin_1.."&\\3a&H"..alfin_1.."&\\4a&H"..shin_1.."&" else instart="\\alpha&H"..alfin_s.."&" inend="\\alpha&H"..alfin_e.."&" onefadin="\\alpha&H"..alfin_1.."&" end if alfin_s~=alfin_e then if oneframe then text=text:gsub("^({\\[^}]-)}","%1"..onefadin.."}") else text=text:gsub("^({\\[^}]-)}","%1"..instart.."\\t("..tim..inend..")}") end end elseif fadin>start-S and z>1 and start==subs[i-1].start_time then killpha() text=text:gsub("^({\\[^}]-)}","%1\\alpha&H"..alfin_s.."&\\t("..tim.."\\alpha&H"..alfin_e.."&)}") end -- fade out dure1=full dure2=E-start if res.time then dure=dure2 full=dure2-dur else dure=dure1 full=full-dur end if fullfadout then out_start=dure-fadout tim=out_start..","..dur.."," else tim="" end alfout_s=tohex(255-(round(dure/fadout*255))) alfout_e=tohex(255-(round(full/fadout*255))) alfout_1=tohex(255-(round((dure+full)/2/fadout*255))) if shad~="00" then shout_s=tohex(255-round((255-tonumber(alfout_s,16))*shade)) shout_e=tohex(255-round((255-tonumber(alfout_e,16))*shade)) shout_1=tohex(255-round((255-tonumber(alfout_1,16))*shade)) outstart="\\1a&H"..alfout_s.."&\\3a&H"..alfout_s.."&\\4a&H"..shout_s.."&" outend="\\1a&H"..alfout_e.."&\\3a&H"..alfout_e.."&\\4a&H"..shout_e.."&" onefadeout="\\1a&H"..alfout_1.."&\\3a&H"..alfout_1.."&\\4a&H"..shout_1.."&" else outstart="\\alpha&H"..alfout_s.."&" outend="\\alpha&H"..alfout_e.."&" onefadeout="\\alpha&H"..alfout_1.."&" end if kill==1 then autstart=outstart else autstart="" end if alfout_s~=alfout_e then if oneframe then text=text:gsub("^({\\[^}]-)}","%1"..onefadeout.."}") else text=text:gsub("^({\\[^}]-)}","%1"..autstart.."\\t("..tim..outend..")}") end end end text=text:gsub("\\fake","") :gsub("{}","") line.text=text subs[i]=line end end function vfade(subs,sel) vfcheck() for z,i in ipairs(sel) do line=subs[i] text=line.text st,et=times() vt=math.floor((fr2ms(vframe+1)+fr2ms(vframe))/2) vfin=vt-st vfut=et-vt if not text:match("\\fad%(") then text="{\\fad(0,0)}"..text text=text:gsub("{\\fad%(0,0%)}{\\","{\\fad(0,0)\\") end if res.vin and vfin>0 then text=text:gsub("\\fad%(%d+,(%d+)%)","\\fad("..vfin..",%1)") end if res.vout and vfut>0 then text=text:gsub("\\fad%((%d+),%d+%)","\\fad(%1,"..vfut..")") end text=text:gsub("\\fad%(0,0%)","") :gsub("{}","") line.text=text subs[i]=line end end function vfcheck() if aegisub.project_properties==nil then t_error("Current frame unknown.\nProbably your Aegisub is too old.\nMinimum required: r8374.",true) end vframe=aegisub.project_properties().video_position if vframe==nil or fr2ms(1)==nil then t_error("Current frame unknown. Probably no video loaded.",true) end end function textmod(orig) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 vis=text:gsub("%b{}","") ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end stags=text:match("^{(\\[^}]-)}") or "" text=text:gsub("^{\\[^}]-}",""):gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n,t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext="{"..stags.."}"..newline text=newtext:gsub("{}","") return text end function killpha() if shad~="00" then text=text:gsub("\\[1234]a&H%x%x&","") end text=text:gsub("\\fad%([%d%.%,]-%)",""):gsub("\\alpha&H%x%x&",""):gsub("\\t%([^\\%)]-%)",""):gsub("{}","") if not text:match("^{\\") then text="{\\fake}"..text end end function times() st=line.start_time et=line.end_time dur=et-st return st,et,dur end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end

-- Config -- function saveconfig() fadconf="Fade config\n\n" for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="dropdown" then fadconf=fadconf..val.name..":"..res[val.name].."\n" end if val.class=="checkbox" and val.name~="save" and val.name~="del" then fadconf=fadconf..val.name..":"..tf(res[val.name]).."\n" end end fadconfig=ADP("?user").."\\apply_fade.conf" file=io.open(fadconfig,"w") file:write(fadconf) file:close() ADD({{class="label",label="Config saved to:\n"..fadconfig}},{"OK"},{close='OK'}) end function loadconfig() fconfig=ADP("?user").."\\apply_fade.conf" file=io.open(fconfig) if file~=nil then konf=file:read("*all") io.close(file) for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="dropdown" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function fadeconfig(subs,sel) GUI={ {x=0,y=0,width=4,class="label",label="fade / alpha/c/blur transform"}, {x=0,y=1,class="label",label="Fade in:"}, {x=0,y=2,class="label",label="Fade out:"}, {x=1,y=1,width=3,class="floatedit",name="fadein",value=0}, {x=1,y=2,width=3,class="floatedit",name="fadeout",value=0}, {x=4,y=0,class="checkbox",name="alf",label="alpha"}, {x=5,y=0,class="checkbox",name="blur",label="blur"}, {x=4,y=1,class="checkbox",name="crl",label="from:"}, {x=4,y=2,class="checkbox",name="clr",label="to:"}, {x=5,y=1,class="color",name="c1"}, {x=5,y=2,class="color",name="c2"}, {x=0,y=3,class="label",label="accel:"}, {x=1,y=3,width=3,class="floatedit",name="inn",value=1,min=0,hint="accel in - <1 starts fast, >1 starts slow"}, {x=4,y=3,width=2,class="floatedit",name="utt",value=1,min=0,hint="accel out - <1 starts fast, >1 starts slow"}, {x=0,y=4,class="label",label="blur:",}, {x=1,y=4,width=3,class="floatedit",name="bli",value=3,min=0,hint="start blur"}, {x=4,y=4,width=2,class="floatedit",name="blu",value=3,min=0,hint="end blur"},

{x=0,y=5,class="label",label="By letter:"}, {x=1,y=5,class="dropdown",name="letterfade", items={"40","80","120","160","200","250","300","350","400","450","500","1000"},value="120"}, {x=2,y=5,width=2,class="label",label="ms/letter"}, {x=4,y=5,class="checkbox",name="rtl",label="rtl",hint="right to left"}, {x=5,y=5,class="checkbox",name="del",label="Delete",hint="delete letter-by-letter"},

{x=0,y=6,width=6,class="checkbox",name="keepthefade",label="Keep fade along with colour transforms"},

{x=0,y=7,width=4,class="checkbox",name="ko",label="Letter by letter using \\ko"}, {x=4,y=7,width=2,class="checkbox",name="word",label="\\ko by word"},

{x=0,y=8,width=4,class="checkbox",name="mult",label="Fade across multiple lines"}, {x=4,y=8,width=2,class="checkbox",name="time",label="Global time"},

{x=0,y=10,width=3,class="checkbox",name="vin",label="Fade in to current frame"}, {x=3,y=10,width=3,class="checkbox",name="vout",label="out from current frame"},

{x=4,y=9,width=2,class="checkbox",name="save",label="[Save config]"}, {x=0,y=9,width=4,class="checkbox",name="rem",label="Remember last settings"}, } loadconfig() if faded and res.rem then for key,val in ipairs(GUI) do if val.class=="checkbox" or val.class=="dropdown" or val.class=="floatedit" then val.value=res[val.name] end end end P,res=ADD(GUI,{"Apply Fade","Letter by Letter","Cancel"},{ok='Apply Fade',cancel='Cancel'}) fr2ms=aegisub.ms_from_frame ms2fr=aegisub.frame_from_ms fadin=res.fadein fadout=res.fadeout faded=true if P=="Apply Fade" then if res.save then saveconfig() elseif res.alf or res.blur or res.clr or res.crl then fadalpha(subs,sel) elseif res.mult then fadeacross(subs,sel) elseif res.vin or res.vout then vfade(subs,sel) else fade(subs,sel) end end if P=="Letter by Letter" then if res.ko or res.word then koko_da(subs,sel) else fade(subs,sel) end end end function apply_fade(subs,sel) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel fadeconfig(subs,sel) aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(apply_fade) else aegisub.register_macro(script_name,script_description,apply_fade) end --[[ Full manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#masquerade

• Masquerade Creates a mask with the selected shape "create mask on a new line" does the obvious and raises the layer of the current line by 1 "remask" only changes an existing mask for another shape without changing tags "Save/delete mask" lets you save a mask from active line or delete one of your custom masks - to save a mask, type a name and the mask from your active line will be saved (appdata/masquerade.masks) - to delete a mask, type its name or type 'del' and select the name from the menu on the left

• Shift Tags Allows you to shift tags by character or by word. For the first block, single tags can be moved right. For inline tags, each block can be moved left or right.

• an8 / q2 (obvious)

• Motion Blur Creates motion blur by duplicating the line and using some alpha. You can set a value for blur or keep the existing blur for each line. 'Distance' is the distance between the \pos coordinates of the resulting 2 lines. If you use 3 lines, the 3rd one will be in the original position, i.e. in the middle. The direction is determined from the first 2 points of a vectorial clip (like with clip2frz/clip2fax).

• Merge Tags Select lines with the SAME TEXT but different tags, and they will be merged into one line with tags from all of them. For example: {\bord2}AB{\shad3}C A{\fs55}BC -> {\bord2}A{\fs55}B{\shad3}C If 2 lines have the same tag in the same place, the value of the later line overrides the earlier one.

• Alpha Shift Makes text appear letter by letter on frame-by-frame lines using alpha&HFF& like this: {alpha&HFF&}text t{alpha&HFF&}ext te{alpha&HFF&}xt tex{alpha&HFF&}t text

• Alpha Time Either select lines that are already timed for alpha timing and need alpha tags, or just one line that needs to be alpha timed. In the GUI, split the line by hitting Enter where you want the alpha tags. If you make no line breaks, text will be split by spaces. Alpha Text is for when you have the lines already timed and just need the tags. Alpha Time is for one line. It will be split to equally long lines with alpha tags added. If you add "@" to your line first, alpha tags will replace the @, and no GUI will pop up. Example text: This @is @a @test.

• Strikealpha Replaces strikeout or underline tags with \alpha&H00& or \alpha&HFF&. Also @. @ -> {\alpha&HFF&} @0 -> {\alpha&H00&} {\u1} -> {\alpha&HFF&} {\u0} -> {\alpha&H00&} {\s0} -> {\alpha&HFF&} {\s1} -> {\alpha&H00&} @E3@ -> {\alpha&HE3&}

--]] script_name="Masquerade" script_description="Masquerade" script_author="unanimated" script_version="2.5" script_namespace="ua.Masquerade" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.5.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' function addmask(subs,sel) for z=#sel,1,-1 do i=sel[z] l=subs[i] text=l.text l.layer=l.layer+1 if res.masknew and not res.remask then if res.mask=="from clip" then if not text:match("\\clip") then ADD({{class="label",label="No clip...",x=1,width=5,height=2}},{"OK"},{close='OK'}) ak() end l.text=nopar("clip",l.text) end subs.insert(i+1,l) end l.layer=l.layer-1 if text:match("\\2c") then mcol="\\c"..text:match("\\2c(&H[%x]+&)") else mcol="" end

-- REMASK if res.remask then if res.mask=="from clip" then masklip() l.text=nopar("clip",l.text) nmask=ctext2 l.text=re_mask(l.text) else for k=1,#allmasks do if allmasks[k].n==res.mask then nmask=allmasks[k].m l.text=re_mask(l.text) end end end else -- STANDARD MASK if res.mask=="from clip" then masklip() l.text="{\\an7\\blur1\\bord0\\shad0\\fscx100\\fscy100"..mcol..mp..pos.."\\p1}"..ctext2 else atags="" org=l.text:match("\\org%b()") if org then atags=atags..org end frz=l.text:match("\\frz[%d%.%-]+") if frz then atags=atags..frz end frx=l.text:match("\\frx[%d%.%-]+") if frx then atags=atags..frx end fry=l.text:match("\\fry[%d%.%-]+") if fry then atags=atags..fry end

l.text=l.text:gsub(".*(\\pos%([%d%,%.%-]-%)).*","%1") if not l.text:match("\\pos") then l.text="" end for k=1,#allmasks do if allmasks[k].n==res.mask then if res.mask:match("alignment grid") then l.text="{\\an7\\bord0\\shad0\\blur0.6"..l.text..atags.."\\c&H000000&\\alpha&H80&\\p1}"..allmasks[k].m else l.text="{\\an7\\bord0\\shad0\\blur1"..l.text..mcol.."\\p1}"..allmasks[k].m end if res.mask=="square" then l.text=l.text:gsub("\\an7","\\an5") end end end if not l.text:match("\\pos") then l.text=l.text:gsub("\\p1","\\pos(640,360)\\p1") end end end subs[i]=l end end function masklip() text=text:gsub("\\clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)","\\clip(m %1 %2 l %3 %2 %3 %4 %1 %4)") if text:match("\\move") then text=text:gsub("\\move","\\pos") mp="\\move" else mp="\\pos" end ctext=text:match("clip%(m ([%d%.%a%s%-]+)") if text:match("\\pos") then pos=text:match("\\pos(%([^%)]+%))") local xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)") xx=round(xx) yy=round(yy) ctext2=ctext:gsub("([%d%.%-]+)%s([%d%.%-]+)",function(a,b) return a-xx.." "..b-yy end) else pos="(0,0)" ctext2=ctext end ctext2="m "..ctext2:gsub("([%d%.]+)",function(a) return round(a) end) end function re_mask(text) text=text :gsub("\\fsc[xy][^}\\]+","") :gsub("}m [^{]+","\\fscx100\\fscy100}"..nmask) if res.mask=="square" then text=text:gsub("\\an7","\\an5") end return text end function savemask(subs,sel,act) masker=ADP("?user").."\\masquerade.masks" file=io.open(masker) if file then masx=file:read("*all") io.close(file) end if masx==nil then masx="" end mask_name=res.maskname masx=masx:gsub(":\n$",":\n\n") :gsub(":$",":\n\n") -- delete mask deleted=0 for m=1,#maasks do if mask_name=="del" then mask_name=res.mask end if maasks[m]==mask_name then if m<=10 then t_error("You can't delete a default mask.",true) else e_name=esc(mask_name) masx=masx:gsub("mask:"..e_name..":.-:\n\n","") t_error("Mask '"..mask_name.."' deleted",false) end deleted=1 end end -- add new mask if deleted==0 then text=subs[act].text text=text:gsub("{[^}]-}","") newmask=text:match("m [%d%s%-mbl]+") newmask=newmask:gsub("%s*$","") if newmask==nil then t_error("No mask detected on active line.",true) end if mask_name=="mask name here" or mask_name=="" then p,rez=ADD({{class="label",label="Enter a proper name for the mask:"}, {y=1,class="edit",name="mname"},},{"OK","Cancel"},{ok='OK',close='Cancel'}) if p=="Cancel" then ak() end if rez.mname=="" then t_error("Naming fail",true) else mask_name=rez.mname end for m=1,#maasks do if maasks[m]==mask_name then t_error("Mask '"..mask_name.."' already exists.",true) end end end new_mask="mask:"..mask_name..":"..newmask..":\n\n" masx=masx..new_mask end masx=masx:gsub(":\nmask",":\n\nmask") file=io.open(masker,"w") file:write(masx) file:close() if deleted==0 then ADD({{class="label",label="Mask '"..mask_name.."' saved to:\n"..masker}},{"OK"},{close='OK'}) end end function add_an8(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text if line.text:match("\\an%d") and res.an8~="q2" then text=text:gsub("\\(an%d)","\\"..res.an8) end if line.text:match("\\an%d")==nil and res.an8~="q2" then text="{\\"..res.an8.."}"..text text=text:gsub("{\\(an%d)}{\\","{\\%1\\") end if res.an8=="q2" then if text:match("\\q2") then text=text:gsub("\\q2","") text=text:gsub("{}","") else text="{\\q2}"..text text=text:gsub("{\\q2}{\\","{\\q2\\") end end line.text=text subs[i]=line end end function alfashift(subs,sel) count=1 for z,i in ipairs(sel) do line=subs[i] text=line.text aa=re.find(text,"\\{\\\\alpha\\&HFF\\&\\}[\\w[:punct:]]") if not aa then t_error("Line "..z.." does not \nappear to have \n\\alpha&&HFF&&",true) end if count>1 then switch=1 repeat text=re.sub(text,"(\\{\\\\alpha\\&HFF\\&\\})([\\w[:punct:]])","\\2\\1") text=text :gsub("({\\alpha&HFF&}) "," %1") :gsub("({\\alpha&HFF&})\\N","\\N%1") :gsub("({\\alpha&HFF&})$","") switch=switch+1 until switch>=count end count=count+1 line.text=text subs[i]=line end end function strikealpha(subs,sel) for z,i in ipairs(sel) do l=subs[i] l.text=l.text :gsub("\\s1","\\alpha&H00&") :gsub("\\s0","\\alpha&HFF&") :gsub("\\u1","\\alpha&HFF&") :gsub("\\u0","\\alpha&H00&") :gsub("@(%x%x)@","{\\alpha&H%1&}") :gsub("@0","{\\alpha&H00&}") :gsub("@","{\\alpha&HFF&}") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") subs[i]=l end end

-- Motion Blur -- function motionblur(subs,sel) mblur=res.mblur mbdist=res.mbdist mbalfa=res.mbalfa mb3=res.mb3 keepblur=res.keepblur for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text if text:match("\\clip%(m") then if not text:match("\\pos") then text=getpos(subs,text) end if not res.keepblur then text=addtag("\\blur"..mblur,text) end text=text:gsub("{%*?\\[^}]-}",function(tg) return duplikill(tg) end) c1,c2,c3,c4=text:match("\\clip%(m ([%-%d%.]+) ([%-%d%.]+) l ([%-%d%.]+) ([%-%d%.]+)") if c1==nil then t_error("There seems to be something wrong with your clip",1) end text=text:gsub("\\clip%b()","") text=addtag3("\\alpha&H"..mbalfa.."&",text) cx=c3-c1 cy=c4-c2 cdist=math.sqrt(cx^2+cy^2) mbratio=cdist/mbdist*2 mbx=round(cx/mbratio*100)/100 mby=round(cy/mbratio*100)/100 text2=text:gsub("\\pos%(([%-%d%.]+),([%-%d%.]+)",function(a,b) return "\\pos("..a-mbx..","..b-mby end) l2=line l2.text=text2 subs.insert(i+1,l2) table.insert(sel,sel[#sel]+1) if res.mb3 then line.text=text subs.insert(i+1,line) table.insert(sel,sel[#sel]+1) end text=text:gsub("\\pos%(([%-%d%.]+),([%-%d%.]+)",function(a,b) return "\\pos("..a+mbx..","..b+mby end) else noclip=true end line.text=text subs[i]=line end if noclip then t_error("Some lines weren't processed - missing clip.\n(2 points of a vectorial clip are needed for motion direction.)") noclip=nil end end

-- Merge tags -- function merge(subs,sel) tk={} tg={} stg="" for z,i in ipairs(sel) do line=subs[i] text=line.text text=text:gsub("{\\\\k0}","") repeat text,c=text until c==0 vis=text:gsub("%b{}","") if z==1 then rt=vis letrz=re.find(rt,".") for l=1,#letrz do table.insert(tk,letrz[l].str) end end if vis~=rt then t_error("Error. Inconsistent text.\nAll selected lines must contain the same text.",true) end stags=text:match("^{(\\[^}]-)}") or "" stg=stg..stags stg=duplikill(stg) text=text:gsub(STAG,"") :gsub("{[^\\}]-}","") count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t newt=duplikill(newt) newt=newt:gsub("%*$","") end end if newt~="" then newline=newline.."{"..newt.."}" end end newtext="{"..stg.."}"..newline newtext=extrakill(newtext,2) line=subs[sel[1]] line.text=newtext subs[sel[1]]=line for z=#sel,2,-1 do subs.delete(sel[z]) end sel={sel[1]} return sel end function shiftag(subs,sel,act) rine=subs[act] tags=rine.text:match(STAG) or "" ftags=tags:gsub("[{}]","") ftags=ftags:gsub("\\%a+%b()","") :gsub("\\an?%d","") cstext=rine.text:gsub(STAG,"") rept={"drept"} -- build GUI shiftab={ {x=0,y=0,width=3,class="label",label="[ Start Tags (Shift Right only) ] ",}, {x=3,y=0,class="label",label="[ Inline Tags ] ",}, } ftw=1 -- regular tags -> GUI for f in ftags:gmatch("\\[^\\]+") do lab=f:gsub("&","&&") cb={x=0,y=ftw,width=2,class="checkbox",name="chk"..ftw,label=lab,value=false,realname=f} table.insert(shiftab,cb) ftw=ftw+1 table.insert(rept,f) end table.insert(shiftab,{x=0,y=ftw+1,class="label",label="Shift by letter /"}) table.insert(shiftab,{x=1,y=ftw+1,class="checkbox",name="word",label="word"}) table.insert(shiftab,{x=0,y=ftw+2,width=2,class="intedit",name="rept",value=1,min=1}) table.insert(shiftab,{x=0,y=ftw+3,width=2,class="checkbox",name="nuke",label="remove selected tags"}) itw=1 -- inline tags if cstext:match(ATAG) then for f in cstext:gmatch(ATAG) do lab=f:gsub("&","&&") if itw==31 then lab="Error: 30 tags max" f="" end if itw==32 then break end cb={x=3,y=itw,class="checkbox",name="chk2"..itw,label=lab,value=false,realname=f} drept=0 for r=1,#rept do if f==rept[r] then drept=1 end end if drept==0 then table.insert(shiftab,cb) itw=itw+1 table.insert(rept,f) end end end repeat if press=="All Inline Tags" then for key,val in ipairs(shiftab) do if val.class=="checkbox" and val.x==3 then val.value=true end if val.class=="checkbox" and val.x<3 then val.value=res[val.name] end end end press,rez=ADD(shiftab,{"Shift Left","Shift Right","All Inline Tags","Cancel"},{ok='Shift Right',close='Cancel'}) until press~="All Inline Tags" if press=="Cancel" then ak() end if press=="Shift Right" then R=true else R=false end if press=="Shift Left" then L=true else L=false end

-- nuke tags if rez.nuke then for key,val in ipairs(shiftab) do if val.class=="checkbox" and rez[val.name] and val.x==0 and val.name~="nuke" then tags=tags:gsub(esc(val.realname),"") rez[val.name]=false end if val.class=="checkbox" and rez[val.name] and val.x==3 then cstext=cstext:gsub(esc(val.realname),"") rez[val.name]=false end end end

-- shift inline tags if R then for s=#shiftab,1,-1 do stab=shiftab[s] if rez[stab.name] and stab.x==3 then stag=stab.realname etag=esc(stag) retag=resc(stag) rep=0 repeat if not rez.word then cstext=re.sub(cstext,retag.."(\\s?[\\w[:punct:]]\\s?)","\\1"..retag) cstext=cstext :gsub(etag.."(%s?\\N%s?)","%1"..stag) :gsub(etag.."(%s?{[^}]-}%s?)","%1"..stag) :gsub(etag.."(%s?\\N%s?)","%1"..stag) else cstext=cstext :gsub(etag.."(%s*%S+%s*)","%1"..stag) :gsub(etag.."(%s?\\N%s?)","%1"..stag) :gsub(etag.."({[^}]-})","%1"..stag) :gsub(etag.."(%s?\\N%s?)","%1"..stag) end rep=rep+1 until rep==rez.rept end end elseif L then for key,val in ipairs(shiftab) do if rez[val.name]==true and val.x==3 then stag=val.realname etag=esc(stag) retag=resc(stag) rep=0 repeat if not rez.word then cstext=re.sub(cstext,"([\\w[:punct:]]\\s?)"..retag,retag.."\\1") cstext=cstext :gsub("(\\N%s?)"..etag,stag.."%1") :gsub("({[^}]-}%s?)"..etag,stag.."%1") :gsub("(\\N%s?)"..etag,stag.."%1") else cstext=cstext :gsub("([^%s]+%s*)"..etag,stag.."%1") :gsub("(\\N%s*)"..etag,stag.."%1") :gsub("(({[^}]-})%s*)"..etag,stag.."%1") :gsub("(\\N%s*)"..etag,stag.."%1") end rep=rep+1 until rep==rez.rept end end cstext=cstext:gsub("{%*?(\\[^}]-)}{%*?(\\[^}]-)}","{%1%2}") end cstext=cstext:gsub(ATAG,function(tg) return duplikill(tg) end)

--shift start tags startags="" for key,val in ipairs(shiftab) do if rez[val.name]==true and val.x==0 and val.name~="nuke" then stag=val.realname etag=esc(stag) if R then startags=startags..stag tags=tags:gsub(etag,"") end end end

if startags~="" and R then cstext="{_T_}"..cstext REP=0 if not rez.word then repeat cstext=re.sub(cstext,"\\{_T_\\}([\\w[:punct:]]\\s*)","\\1\\{_T_\\}") cstext=cstext :gsub("{_T_}(\\N%s?)","%1{_T_}") :gsub("{_T_}({[^}]-}%s*)","%1{_T_}") :gsub("{_T_}(\\N%s?)","%1{_T_}") REP=REP+1 until REP==rez.rept else repeat cstext=cstext :gsub("{_T_}(%s*[^%s]+%s*)","%1{_T_}") :gsub("{_T_}(%s?\\N%s?)","%1{_T_}") :gsub("{_T_}({[^}]-})","%1{_T_}") :gsub("{_T_}(%s?\\N%s?)","%1{_T_}") REP=REP+1 until REP==rez.rept end cstext=cstext :gsub("_T_",startags) :gsub("{(%*?\\[^}]-)}{(%*?\\[^}]-)}","{%1%2}") end

text=tags..cstext text=text:gsub("{(%*?\\[^}]-)}{(%*?\\[^}]-)}","{%1%2}") :gsub("^{}","") :gsub(ATAG,function(tg) return duplikill(tg) end) :gsub("^{%*","{")

rine.text=text subs[act]=rine end function alfatime(subs,sel) -- collect / check text for z,i in ipairs(sel) do text=subs[i].text if z==1 then alfatext=text:gsub(STAG,"") end if z~=1 then alfatext2=text:gsub(STAG,"") if alfatext2~=alfatext then t_error("Text must be the same \nfor all selected lines",true) end end end

if not alfatext:match("@") then -- GUI atGUI={{x=0,y=0,width=5,height=8,class="textbox",name="alfa",value=alfatext}, {x=0,y=8,class="label",label="Break the text with 'Enter' the way it should be alpha-timed. (lines selected: "..#sel..")"},} pressed,rez=ADD(atGUI,{"Alpha Text","Alpha Time","Cancel"},{ok='Alpha Text',close='Cancel'}) if pressed=="Cancel" then ak() end data=rez.alfa else data=alfatext:gsub("@","\n") pressed="Alpha Time" end if not data:match("\n") then data=data:gsub(" "," \n") end -- sort data into a table altab={} data=data.."\n" ac=1 data2="" for al in data:gmatch("(.-\n)") do al2=al:gsub("\n","{"..ac.."}") ac=ac+1 data2=data2..al2 if al~="" then table.insert(altab,al2) end end

-- apply alpha text if pressed=="Alpha Text" then for z,i in ipairs(sel) do altxt="" for a=1,z do altxt=altxt..altab[a] end esctxt=esc(altxt) line=subs[i] text=line.text if altab[z]~=nil then tags=text:match(STAG) or "" text=data2 :gsub("\n","") :gsub(esctxt,altxt.."{\\alpha&HFF&}") :gsub("({\\alpha&HFF&}.-){\\alpha&HFF&}","%1") :gsub("{\\alpha&HFF&}$","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("{%d+}","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") text=tags..text end line.text=text subs[i]=line end end

-- apply alpha text + split line if pressed=="Alpha Time" then line=subs[sel[1]] start=line.start_time endt=line.end_time dur=endt-start f=dur/#altab for a=#altab,1,-1 do altxt="" altxt=altxt..altab[a] esctxt=esc(altxt) line.text=line.text:gsub("@","") line2=line tags=line2.text:match(STAG) or "" line2.text=data2 :gsub("\n","") :gsub(esctxt,altxt.."{\\alpha&HFF&}") :gsub("({\\alpha&HFF&}.-){\\alpha&HFF&}","%1") :gsub("{\\alpha&HFF&}$","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("{%d+}","") line2.text=tags..line2.text line2.start_time=start+f*(a-1) line2.end_time=start+f+f*(a-1) subs.insert(sel[1]+1,line2) end subs.delete(sel[1]) end end

-- reanimatools -- function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function resc(str) str=str:gsub("[%%%(%)%[%]%.%*%-%+%?%^%$\\{}]","\\%1") return str end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function addtag2(tag,text) -- mask version tg=tag:match("\\%d?%a+") text=text:gsub("^{(\\[^}]-)}","{"..tag.."%1}") :gsub("("..tg.."[^\\}]+)([^}]-)("..tg.."[^\\}]+)","%2%1") return text end function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") val="[%d%-&]" if not tgt then tgt=tg:match("(\\%d?%a+)%b()") val="%b()" end if not tgt then tgt=tg:match("\\fn") val="" end if not tgt then t_error("adding tag '"..tg.."' failed.") end if tgt:match("clip") then txt,r=txt:gsub("^({[^}]-)\\i?clip%b()","%1"..tg) if r==0 then txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end elseif no_tf:match("^({[^}]-)"..tgt..val) then txt=txt:gsub("^({[^}]-)"..tgt..val.."[^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function getpos(subs,text) for i=1,#subs do if subs[i].class=="info" then local k=subs[i].key local v=subs[i].value if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if resx==nil then resx=0 end if resy==nil then resy=0 end if subs[i].class=="style" then local st=subs[i] if st.name==line.style then acleft=st.margin_l if line.margin_l>0 then acleft=line.margin_l end acright=st.margin_r if line.margin_r>0 then acright=line.margin_r end acvert=st.margin_t if line.margin_t>0 then acvert=line.margin_t end acalign=st.align if text:match("\\an%d") then acalign=text:match("\\an(%d)") end aligntop="789" alignbot="123" aligncent="456" alignleft="147" alignright="369" alignmid="258" if alignleft:match(acalign) then horz=acleft h_al="left" elseif alignright:match(acalign) then horz=resx-acright h_al="right" elseif alignmid:match(acalign) then horz=resx/2 h_al="mid" end if aligntop:match(acalign) then vert=acvert v_al="top" elseif alignbot:match(acalign) then vert=resy-acvert v_al="bottom" elseif aligncent:match(acalign) then vert=resy/2 v_al="mid" end break end end end if horz>0 and vert>0 then if not text:match("^{\\") then text="{\\rel}"..text end text=text:gsub("^({\\[^}]-)}","%1\\pos("..horz..","..vert..")}") :gsub("\\rel","") end return text end function nopar(tag,t) t=t:gsub("\\"..tag.."%b()","") :gsub("{}","") return t end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function masquerade(subs,sel,act) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" defmasks="mask:square:m 0 0 l 100 0 100 100 0 100:\n\nmask:rounded square:m -100 -25 b -100 -92 -92 -100 -25 - 100 l 25 -100 b 92 -100 100 -92 100 -25 l 100 25 b 100 92 92 100 25 100 l -25 100 b -92 100 -100 92 -100 25 l -100 - 25:\n\nmask:rounded square 2:m -100 -60 b -100 -92 -92 -100 -60 -100 l 60 -100 b 92 -100 100 -92 100 -60 l 100 60 b 100 92 92 100 60 100 l -60 100 b -92 100 -100 92 -100 60 l -100 -60:\n\nmask:rounded square 3:m -100 -85 b -100 -96 -96 -100 -85 -100 l 85 -100 b 96 -100 100 -96 100 -85 l 100 85 b 100 96 96 100 85 100 l -85 100 b -96 100 -100 96 - 100 85 l -100 -85:\n\nmask:circle:m -100 -100 b -45 -155 45 -155 100 -100 b 155 -45 155 45 100 100 b 46 155 -45 155 -100 100 b -155 45 -155 -45 -100 -100:\n\nmask:equilateral triangle:m -122 70 l 122 70 l 0 -141:\n\nmask:right-angled triangle:m -70 50 l 180 50 l -70 -100:\n\nmask:alignment grid:m -500 -199 l 500 -199 l 500 -201 l -500 -201 m -701 1 l 700 1 l 700 -1 l -701 -1 m -500 201 l 500 201 l 500 199 l -500 199 m -1 -500 l 1 -500 l 1 500 l -1 500 m -201 -500 l - 199 -500 l -199 500 l -201 500 m 201 500 l 199 500 l 199 -500 l 201 -500 m -150 -25 l 150 -25 l 150 25 l -150 25:\n\nmask:alignment grid 2:m -500 -199 l 500 -199 l 500 -201 l -500 -201 m -701 1 l 700 1 l 700 -1 l -701 -1 m -500 201 l 500 201 l 500 199 l -500 199 m -1 -500 l 1 -500 l 1 500 l -1 500 m -201 -500 l -199 -500 l -199 500 l -201 500 m 201 500 l 199 500 l 199 -500 l 201 -500 m -150 -25 l 150 -25 l 150 25 l -150 25 m -401 -401 l 401 -401 l 401 401 l -401 401 m -399 -399 l -399 399 l 399 399 l 399 -399:\n\n" maasks={"from clip"} allmasks={} masker=ADP("?user").."\\masquerade.masks" file=io.open(masker) if file then masx=file:read("*all") io.close(file) else masx="" end masklist=defmasks..masx for nam,msk in masklist:gmatch("mask:(.-):(.-):") do table.insert(maasks,nam) table.insert(allmasks,{n=nam,m=msk}) end GUI={ {x=0,y=0,class="label",label="Mask:",}, {x=1,y=0,class="dropdown",name="mask",items=maasks,value="square"}, {x=0,y=1,width=2,class="checkbox",name="masknew",label="create mask on a new line",value=true}, {x=2,y=1,width=3,class="checkbox",name="remask",label="remask",value=false},

{x=11,y=0,class="checkbox",name="save",label="Save/delete mask ",value=false}, {x=11,y=1,width=2,class="edit",name="maskname",value="mask name here",hint="Type name of the mask you want to save/delete"},

{x=3,y=0,class="dropdown",name="an8",items={"q2","an1","an2","an3","an4","an5","an6","an7","an8","an9"},value= "an8"},

{x=10,y=0,height=2,class="label",label=":\n:\n:",},

{x=5,y=0,class="label",label="blur:"}, {x=6,y=0,class="floatedit",name="mblur",value=mblur or 3,hint="motion blur"}, {x=7,y=0,width=3,class="checkbox",name="keepblur",label="Keep current",value=keepblur,hint="keep current blur"},

{x=5,y=1,class="label",label="dist:"}, {x=6,y=1,class="floatedit",name="mbdist",value=mbdist or 6,hint="distance between positions"}, {x=7,y=1,class="label",label="@ "}, {x=8,y=1,class="dropdown",name="mbalfa",value=mbalfa or "80",items={"00","20","40","60","80","A0","C0","D0"},hint="alpha"}, {x=9,y=1,class="checkbox",name="mb3",label="3L",value=mb3,hint="use 3 lines instead of 2"},

{x=12,y=0,height=0,class="label",label="Masquerade "..script_version}, } P,res=ADD(GUI, {"masquerade","shift tags","an8 / q2","motion blur","merge tags","alpha shift","alpha time","strikealpha","cancel"},{cancel='cancel'}) if P=="cancel" then ak() end if P=="masquerade" and not res.save then addmask(subs,sel) end if P=="masquerade" and res.save then savemask(subs,sel,act) end if P=="strikealpha" then strikealpha(subs,sel) end if P=="an8 / q2" then add_an8(subs,sel,act) end if P=="alpha shift" then alfashift(subs,sel) end if P=="alpha time" then alfatime(subs,sel) end if P=="motion blur" then motionblur(subs,sel) end if P=="merge tags" then sel=merge(subs,sel) end if P=="shift tags" then shiftag(subs,sel,act) end aegisub.set_undo_point(script_name) return sel,act end if haveDepCtrl then depRec:registerMacro(masquerade) else aegisub.register_macro(script_name,script_description,masquerade) end --[[ "Blur / Layers" creates layers with blur. Supports 2 borders, xbord, ybord, xshad, and yshad. Basic support for transforms and \r. "Blur + Glow" - Same as above but with an extra layer for glow. Set blur amount and alpha for the glow. The "double border" option additionally lets you change the size and colour of the 2nd border. If blur is missing, default blur is added. "Bottom blur" allows you to use different blur for the lowest non-glow layer than for top layer(s). "fix \\1a for layers with border and fade" - Uses \1a&HFF& for the duration of a fade on layers with border. "transition" - for \fad(500,0) with transition 80ms you get \1a&HFF&\t(420,500,\1a&H00&). "only add glow" - will add glow to a line with a border, without messing with the primary / border. (Blur + Glow) "only add 2nd border" - will add 2nd border, without messing with the primary / first border. (Blur / Layers) "Fix fades" - Recalculates those \1a fades mentioned above. Use this when you shift something like an episode title to a new episode and the duration of the sign is different. "Change layer" - raises or lowers layer for all selected lines by the same amount. [This is separate from the other functions.]

Full manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#blurglow ]] script_name="Blur and Glow" script_description="Add blur and/or glow to signs" script_author="unanimated" script_url="http://unanimated.xtreemhost.com/ts/blur-and-glow.lua" script_version="2.5" script_namespace="ua.BlurAndGlow" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.5.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end function glow(subs,sel) if not res.rep then al=res.alfa bl=res.blur end if res.glowcol then glowc=res.glc:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") end if res.autod then if res.clr or res.bsize then res.double=true end end for z=#sel,1,-1 do i=sel[z] progress("Glowing line: "..(#sel-z+1).."/"..#sel) line=subs[i] text=line.text if defaref and line.style=="Default" then sr=defaref elseif lastref and laststyle==line.style then sr=lastref else sr=stylechk(line.style) end lastref=sr laststyle=line.style duration=line.end_time-line.start_time

-- get colors, border, shadow from style stylinfo(text) text=preprocess(text) line.text=text if border~="0" or text:match("\\[xy]bord") then

-- WITH TWO BORDERS if res.double then

-- second border line1=line line1.text=text line1.text=borderline2(line1.text) line1.layer=line1.layer+1 subs.insert(i+1,line1)

-- first border line2=line line2.text=text line2.text=borderline(line2.text) if shadow~="0" then line2.text=line2.text:gsub("^({\\[^}]+)}","%1\\shad"..shadow.."}") end if not res.s_mid then line2.text=line2.text:gsub("^({\\[^}]-)}","%1\\4a&HFF&}") end line2.layer=line2.layer+1 subs.insert(i+2,line2)

-- top line line3=line line3.text=text line3.text=topline(line3.text) line3.layer=line3.layer+1 subs.insert(i+3,line3)

-- bottom / glow text=borderline2(text) text=glowlayer(text,"3c","3") if res.botalpha and line.text:match("\\fad%(") then text=botalfa(text) end line.layer=line.layer-3 line.text=text sls=3

else -- WITH ONE BORDER

-- border line2=line if not res.onlyg then line2.text=text line2.text=borderline(line2.text) end line2.layer=line2.layer+1 subs.insert(i+1,line2)

-- top line line3=line line3.layer=line3.layer+1 if not res.onlyg then line3.text=text line3.text=topline(line3.text) subs.insert(i+2,line3) end

-- bottom / glow text=glowlayer(text,"3c","3") if res.botalpha and line.text:match("\\fad%(") then text=botalfa(text) end line.layer=line.layer-2 line.text=text sls=2

end

else -- WITHOUT BORDER

line2=line line2.layer=line2.layer+1 subs.insert(i+1,line2) text=glowlayer(text,"c","1") line.layer=line.layer-1 line.text=text sls=1

end subs[i]=line for s=z,#sel do sel[s]=sel[s]+sls end end progress("Blur & Glow: DONE") return sel end function layerblur(subs,sel) if res.autod then if res.clr or res.bsize then res.double=true end end for z=#sel,1,-1 do i=sel[z] progress("Blurring line: "..(#sel-z+1).."/"..#sel) line=subs[i] text=line.text if defaref~=nil and line.style=="Default" then sr=defaref elseif lastref~=nil and laststyle==line.style then sr=lastref else sr=stylechk(line.style) end lastref=sr laststyle=line.style duration=line.end_time-line.start_time

-- get colors, border, shadow from style stylinfo(text) text=preprocess(text) line.text=text

-- TWO BORDERS if res.double then

-- first border line2=line if not res.onlyb then line2.text=text line2.text=borderline(line2.text) if not res.s_mid then line2.text=line2.text:gsub("^({\\[^}]-)}","%1\\4a&HFF&}") end end line2.layer=line2.layer+1 subs.insert(i+1,line2)

-- top line line3=line line3.layer=line3.layer+1 if not res.onlyb then line3.text=text line3.text=topline(line3.text) subs.insert(i+2,line3) end

-- second border text=borderline2(text) line.layer=line.layer-2 line.text=text sls=2

-- ONE BORDER else

-- top line line3=line line3.text=text line3.text=topline(line3.text) line3.layer=line3.layer+1 subs.insert(i+1,line3)

-- bottom line text=borderline(text) line.layer=line.layer-1 line.text=text sls=1 end

subs[i]=line for s=z,#sel do sel[s]=sel[s]+sls end end progress("Blur: DONE") end function topline(txt) txt=txt :gsub("(\\t%([^%)]*)\\bord[%d%.]+","%1") :gsub("(\\t%([^%)]*)\\shad[%d%.]+","%1") :gsub("\\t%([^\\]*%)","") if not txt:match("^{[^}]-\\bord") then txt=txt:gsub("^{\\","{\\bord0\\") end txt=txt :gsub("\\bord[%d%.]+","\\bord0") :gsub("(\\r[^}]-)}","%1\\bord0}") txt=txt:gsub("(\\[xy]bord)[%d%.]+","") :gsub("\\3c&H%x+&","") if shadow~="0" then txt=txt:gsub("^({\\[^}]+)}","%1\\shad"..shadow.."}") end txt=txt :gsub("^({\\[^}]-)}","%1\\4a&HFF&}") :gsub("(\\r[^}]-)}","%1\\shad"..shadow.."\\4a&HFF&}") :gsub("\\bord[%d%.%-]+([^}]-)(\\bord[%d%.%-]+)","%1%2") :gsub("\\shad[%d%.%-]+([^}]-)(\\shad[%d%.%-]+)","%1%2") if res.s_top then txt=txt:gsub("\\4a&HFF&","") end txt=txt:gsub("{}","") return txt end function borderline(txt) txt=txt:gsub("\\c&H%x+&","") -- transform check if txt:match("^{[^}]-\\t%([^%)]-\\3c") then pretrans=text:match("^{(\\[^}]-)\\t") if not pretrans:match("^{[^}]-\\3c") then txt=txt:gsub("^{\\","{\\c"..soutline.."\\") end end if not txt:match("^{[^}]-\\3c&[^}]-}") then txt=txt:gsub("^({\\[^}]+)}","%1\\c"..soutline.."}") :gsub("(\\r[^}]-)}","%1\\c"..routline.."}") end txt=txt:gsub("(\\3c)(&H%x+&)","%1%2\\c%2") :gsub("(\\r[^}]-)}","%1\\c"..routline.."}") :gsub("(\\r[^}]-\\3c)(&H%x+&)([^}]-)}","%1%2\\c%2%3") :gsub("\\c&H%x+&([^}]-)(\\c&H%x+&)",function(a,b) if not a:match("\\t") then return a..b end end) :gsub("{%*?}","") if res.bbl and not res.double then txt=txt:gsub("\\blur[%d%.]+","\\blur"..res.bblur) end if res.botalpha and txt:match("\\fad%(") then txt=botalfa(txt) end return txt end function borderline2(txt) outlinetwo=primary if res.clr then col3=res.c3:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") outlinetwo=col3 rimary=col3 end bordertwo=border if res.bsize then bordertwo=res.secbord end -- transform check if txt:match("^{[^}]-\\t%([^%)]-\\bord") then pretrans=text:match("^{(\\[^}]-)\\t") if not pretrans:match("^{[^}]-\\bord") then txt=txt:gsub("^{\\","{\\bord"..border.."\\") end end if not txt:match("^{[^}]-\\bord") then txt=txt:gsub("^{\\","{\\bord"..border.."\\") end txt=txt:gsub("(\\r[^\\}]-)([\\}])","%1\\bord"..rbord.."%2") :gsub("(\\r[^\\}]-)\\bord[%d%.%-]+([^}]-)(\\bord[%d%.%-]+)","%1%2%3") :gsub("(\\bord)([%d%.]+)",function(a,b) if res.bsize then brd=bordertwo else brd=b end return a..b+brd end) :gsub("(\\[xy]bord)([%d%.]+)",function(a,b) return a..b+b end) :gsub("\\3c&H%x+&","") :gsub("^({\\[^}]+)}","%1\\3c"..outlinetwo.."}") :gsub("(\\3c)(&H%x+&)","%1"..outlinetwo) if res.clr then txt=txt:gsub("\\c&H%x+&([^}]-)}","\\c"..rimary.."\\3c"..outlinetwo.."%1}") else txt=txt:gsub("(\\c)(&H%x+&)([^}]-)}","%1%2%3\\3c%2}") end txt=txt:gsub("(\\r[^}]+)}","%1\\3c"..rimary.."}") :gsub("\\c&H%x+&([^}]-)(\\c&H%x+&)",function(a,b) if not a:match("\\t") then return a..b end end) :gsub("\\3c&H%x+&([^}]-)(\\3c&H%x+&)",function(a,b) if not a:match("\\t") then return a..b end end) :gsub("{%*?}","") if res.bbl and res.double then txt=txt:gsub("\\blur[%d%.]+","\\blur"..res.bblur) end if res.botalpha and txt:match("\\fad%(") then txt=botalfa(txt) end return txt end function glowlayer(txt,kol,alf) txt=txt:gsub("\\alpha&H(%x%x)&",function(a) if a>al then return "\\alpha&H"..a.."&" else return "\\alpha&H"..al.."&" end end) :gsub("\\"..alf.."a&H(%x%x)&",function(a) if a>al then return "\\"..alf.."a&H"..a.."&" else return "\\"..alf.."a&H"..al.."&" end end) :gsub("(\\blur)[%d%.]*([\\}])","%1"..bl.."%2") :gsub("(\\r[^}]-)}","%1\\alpha&H"..al.."&}") if not txt:match("^{[^}]-\\alpha") then txt=txt:gsub("^({\\[^}]-)}","%1\\alpha&H"..al.."&}") end if res.alfa=="00" then txt=txt:gsub("^({\\[^}]-)\\alpha&H00&","%1") end txt=txt:gsub("{%*?}","") if res.glowcol then if txt:match("^{\\[^}]-\\"..kol.."&") then txt=txt:gsub("\\"..kol.."&H%x+&","\\"..kol..glowc) else txt=txt:gsub("\\"..kol.."&H%x+&","\\"..kol..glowc) txt=txt:gsub("^({\\[^}]-)}","%1\\"..kol..glowc.."}") end end return txt end function botalfa(txt) fadin,fadout=txt:match("\\fad%((%d+)%,(%d+)") alfadin=res.alphade alfadout=res.alphade if res.alphade=="max" then alfadin=fadin alfadout=fadout end if fadin==nil or fadout==nil then aegisub.log("\n ERROR: Failed to capture fade times from line:\n "..text) end if fadin~="0" then txt=txt:gsub("^({\\[^}]-)}","%1\\1a&HFF&\\t("..fadin-alfadin..","..fadin..",\\1a&H00&)}") end if fadout~="0" then txt=txt:gsub("^({\\[^}]-)}","%1\\t("..duration-fadout..","..duration-fadout+alfadout..",\\1a&HFF&)}") end return txt end function stylinfo(text) startags=text:match("^{\\[^}]-}") or "" startags=startags:gsub("\\t%b()","")

primary=startags:match("^{[^}]-\\c(&H%x+&)") or sr.color1:gsub("H%x%x","H") soutline=sr.color3:gsub("H%x%x","H") outline=startags:match("^{[^}]-\\3c(&H%x+&)") or soutline border=startags:match("^{[^}]-\\bord([%d%.]+)") or tostring(sr.outline) shadow=startags:match("^{[^}]-\\shad([%d%.]+)") or tostring(sr.shadow)

if text:match("\\r%a") then rstyle=text:match("\\r([^\\}]+)") reref=stylechk(rstyle) rimary=reref.color1:gsub("H%x%x","H") routline=reref.color3:gsub("H%x%x","H") rbord=tostring(reref.outline) else routline=soutline rimary=primary rbord=border end end function preprocess(text) if not text:match("^{\\") then text="{\\blur"..bdef.."}"..text -- default blur if no tags text=text:gsub("(\\r[^}]-)}","%1\\blur"..bdef.."}") end if not text:match("\\blur") then text=text:gsub("^{\\","{\\blur"..bdef.."\\") -- default blur if missing in tags text=text:gsub("(\\r[^}]-)}","%1\\blur"..bdef.."}") end if text:match("\\blur") and not text:match("^{[^}]*blur[^}]*}") then -- add blur if missing in first tag block text=text:gsub("^{\\","{\\blur"..bdef.."\\") end if text:match("^{[^}]-\\t[^}]-}") and not text:match("^{[^}]-\\3c[^}]-\\t") then -- \t workaround text=text:gsub("^{\\","{\\3c"..soutline.."\\") end text=text:gsub("\\1c","\\c") return text end function fixfade(subs,sel) for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text sr=stylechk(line.style) duration=line.end_time-line.start_time border=tostring(sr.outline) bord=text:match("^{[^}]-\\bord([%d%.]+)") if bord then border=bord end

if border~="0" and line.text:match("\\fad%(") then text=text:gsub("\\1a&H%x+&","") :gsub("\\t%([^\\%(%)]-%)","") text=botalfa(text) end line.text=text subs[i]=line end end function layeraise(subs,sel) for z=#sel,1,-1 do i=sel[z] line=subs[i] if line.layer+res["layer"]>=0 then line.layer=line.layer+res["layer"] else t_error("You're dumb. Layers can't go below 0.",1) end subs[i]=line end end function styleget(subs) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i]) end if subs[i].class=="dialogue" then break end end end function stylechk(sn) for s=1,#styles do if sn==styles[s].name then sr=styles[s] if styles[s].name=="Default" then defaref=styles[s] end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function saveconfig() bgconf="Blur & Glow config\n\n" for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="dropdown" or val.class=="color" then bgconf=bgconf..val.name..":"..res[val.name].."\n" end if val.class=="checkbox" and val.name~="save" then bgconf=bgconf..val.name..":"..tf(res[val.name]).."\n" end end blurkonfig=ADP("?user").."\\blurandglow.conf" file=io.open(blurkonfig,"w") file:write(bgconf) file:close() ADD({{class="label",label="Config saved to:\n"..blurkonfig}},{"OK"},{close='OK'}) end function loadconfig() blurkonfig=ADP("?user").."\\blurandglow.conf" file=io.open(blurkonfig) if file~=nil then konf=file:read("*all") io.close(file) for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="dropdown" or val.class=="color" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function blurandglow(subs,sel) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel GUI={ --left {x=0,y=0,width=2,class="label",label=" = Blur and Glow version "..script_version.." ="}, {x=0,y=1,class="label",label="Glow blur:"}, {x=0,y=2,class="label",label="Glow alpha:"},

{x=1,y=1,width=2,class="floatedit",name="blur",value=3},

{x=1,y=2,width=2,class="dropdown",name="alfa",items={"00","20","30","40","50","60","70","80","90","A0","B0","C 0","D0","F0"},value="80"},

{x=0,y=3,class="checkbox",name="glowcol",label="glow c.:",hint="glow colour"}, {x=1,y=3,width=2,class="color",name="glc"},

{x=0,y=4,width=2,class="checkbox",name="s_top",label="keep shadow on top layer"},

{x=0,y=5,width=5,class="checkbox",name="botalpha",label="fix \\1a for layers with border and fade --> transition:",value=true, hint="uses \\1a&HFF& for bottom layer during fade"}, {x=5,y=5,class="dropdown",name="alphade",items={0,45,80,120,160,200,"max"},value=45}, {x=6,y=5,width=2,class="label",label="ms"},

{x=0,y=6,width=4,class="checkbox",name="onlyg",label="only add glow (layers w/ border)"},

-- right {x=4,y=0,class="checkbox",name="double",label="double border"}, {x=5,y=0,width=2,class="checkbox",name="onlyb",label="only add 2nd border"},

{x=4,y=1,class="checkbox",name="bbl",label="bottom blur:", hint="Blur for bottom layer \n[not the glow layer] \nif different from top layer."}, {x=5,y=1,width=2,class="floatedit",name="bblur",value=1},

{x=4,y=2,class="checkbox",name="bsize",label="2nd b. size:", hint="Size for 2nd border \n[counts from first border out] \nif different from the current border."}, {x=5,y=2,width=2,class="floatedit",name="secbord",value=2},

{x=4,y=3,class="checkbox",name="clr",label="2nd b. colour:",hint="Colour for 2nd border \nif different from primary."}, {x=5,y=3,width=2,class="color",name="c3"},

{x=4,y=4,width=4,class="checkbox",name="s_mid",label="keep shadow on middle layer"},

{x=4,y=6,class="label",label=" Change layer:"}, {x=5,y=6,class="dropdown",name="layer",items={"-5","-4","-3","-2","- 1","+1","+2","+3","+4","+5"},value="+1"},

{x=0,y=7,width=2,class="checkbox",name="rep",label="repeat with last settings"}, {x=4,y=7,class="checkbox",name="autod",label="auto double",value=true, hint="automatically use double border\nif 2nd colour or 2nd border size is checked"},

{x=6,y=6,class="dropdown",name="def",items={"0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"},value="0.6",hint="confi g: default blur"}, {x=5,y=7,width=2,class="checkbox",name="save",label="save configuration"}, } loadconfig() buttons={"Blur / Layers","Blur + Glow","Fix fades","Change layer","cancel"} pressed,res=ADD(GUI,buttons,{ok='Blur / Layers',close='cancel'}) if pressed=="cancel" then ak() end bdef=res.def if res.onlyg then res.double=false end if res.onlyb then res.double=true end if res.save then saveconfig() else if res.rep then res=lastres end styleget(subs) if pressed=="Blur / Layers" then layerblur(subs,sel) end if pressed=="Blur + Glow" then sel=glow(subs,sel) end if pressed=="Fix fades" then fixfade(subs,sel) end if pressed=="Change layer" then layeraise(subs,sel) end end if res.rep==false then lastres=res end aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(blurandglow) else aegisub.register_macro(script_name,script_description,blurandglow) end

--[[ Copyright (c) 2009 Muhammad Lukman Nasaruddin (ai-chan) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Automation script: Gradient Factory ]] script_name = "Gradient Factory" script_description = "Color gradient generator by ai-chan" script_author = "Muhammad Lukman Nasaruddin (ai-chan)" script_version = "1.2" script_modified = "19 February 2009" include("karaskel.lua") if not gradient then gradient = {} end gradient.conf = { [1] = { class = "label"; x = 0; y = 0; height = 1; width = 5; label = string.format("%s %s by ai-chan (updated %s)", script_name, script_version, script_modified) } , [3] = { label = "Apply to"; class = "label"; x = 0; y = 1; height = 1; width = 1 } , [4] = { name = "applyto"; class = "dropdown"; x = 1; y = 1; height = 1; width = 4; items = { }; value = nil } , [5] = { label = "Mode"; class = "label"; x = 0; y = 2; height = 1; width = 1 } , [6] = { name = "mode"; class = "dropdown"; x = 1; y = 2; height = 1; width = 4; items = { "Smooth", "Smooth (Vertical)", "By character", "By syllable" }; value = "Smooth" } , [8] = { label = "Pixel per strip (for Smooth modes)"; class = "label"; x = 0; y = 3; height = 1; width = 3 } , [9] = { name = "stripx"; class = "intedit"; x = 3; y = 3; height = 1; width = 2; min = 1; max = 100; value = 10 } , [10] = { label = "Karaoke"; class = "label"; x = 0; y = 4; height = 1; width = 1 } , [11] = { name = "karamode"; class = "dropdown"; x = 1; y = 4; height = 1; width = 4; items = { "As Is", "Strip karaoke", "\\k", "\\kf", "\\ko" }; value = "As Is" } , [12] = { label = "Primary"; class = "label"; x = 1; y = 5; height = 1; width = 1 } , [13] = { name = "primary_mode"; class = "dropdown"; x = 1; y = 6; height = 1; width = 1; items = { }; value = "Ignore" } , [14] = { label = "Secondary"; class = "label"; x = 2; y = 5; height = 1; width = 1 } , [15] = { name = "secondary_mode"; class = "dropdown"; x = 2; y = 6; height = 1; width = 1; items = { }; value = "Ignore" } , [16] = { label = "Outline"; class = "label"; x = 3; y = 5; height = 1; width = 1 } , [17] = { name = "outline_mode"; class = "dropdown"; x = 3; y = 6; height = 1; width = 1; items = { }; value = "Ignore" } , [18] = { label = "Shadow"; class = "label"; x = 4; y = 5; height = 1; width = 1 } , [19] = { name = "shadow_mode"; class = "dropdown"; x = 4; y = 6; height = 1; width = 1; items = { }; value = "Ignore" } , [20] = { label = "Colors"; class = "label"; x = 0; y = 6; height = 1; width = 1 } , [21] = { label = "Color 1"; class = "label"; x = 0; y = 7; height = 1; width = 1 } , [22] = { name = "primary_color1"; class = "color"; x =1; y = 7; height = 1; width = 1 } , [23] = { name = "secondary_color1"; class = "color"; x = 2; y = 7; height = 1; width = 1 } , [24] = { name = "outline_color1"; class = "color"; x = 3; y = 7; height = 1; width = 1 } , [25] = { name = "shadow_color1"; class = "color"; x = 4; y = 7; height = 1; width = 1 } , [26] = { label = "Color 2"; class = "label"; x = 0; y = 8; height = 1; width = 1 } , [27] = { name = "primary_color2"; class = "color"; x = 1; y = 8; height = 1; width = 1 } , [28] = { name = "secondary_color2"; class = "color"; x = 2; y = 8; height = 1; width = 1 } , [29] = { name = "outline_color2"; class = "color"; x = 3; y = 8; height = 1; width = 1 } , [30] = { name = "shadow_color2"; class = "color"; x = 4; y = 8; height = 1; width = 1 } , [31] = { label = "Color 3"; class = "label"; x = 0; y = 9; height = 1; width = 1 } , [32] = { name = "primary_color3"; class = "color"; x = 1; y = 9; height = 1; width = 1 } , [33] = { name = "secondary_color3"; class = "color"; x = 2; y = 9; height = 1; width = 1 } , [34] = { name = "outline_color3"; class = "color"; x = 3; y = 9; height = 1; width = 1 } , [35] = { name = "shadow_color3"; class = "color"; x = 4; y = 9; height = 1; width = 1 } } gradient.config_keys = { 4, 6, 9, 11, 13, 15, 17, 19 } gradient.last_run = 0 gradient.color_comp_count = 3 gradient.colorkeys = { [1] = "primary"; [2] = "secondary"; [3] = "outline"; [4] = "shadow" } function gradient.save_config(config) for _, i in ipairs(gradient.config_keys) do gradient.conf[i].value = config[gradient.conf[i].name] end for j = 1, gradient.color_comp_count do local jk = 17 + 5*j for k = jk, jk + 3 do gradient.conf[k].value = config[gradient.conf[k].name] end end end function gradient.serialize_config(config) local scfg = string.format("%d ", gradient.last_run) for _, i in ipairs(gradient.config_keys) do scfg = scfg .. config[gradient.conf[i].name] .. string.char(2) end for j = 1, gradient.color_comp_count do local jk = 17 + 5*j for k = jk, jk + 3 do scfg = scfg .. config[gradient.conf[k].name] .. string.char(2) end end return string.trim(scfg) end function gradient.unserialize_config(scfg) local c = 0 local cfgtime, scfg = string.headtail(string.trim(scfg)) local keys_count = #gradient.config_keys if tonumber(cfgtime) > gradient.last_run then for g in string.gmatch(scfg, "[^"..string.char(2).."]+") do c = c + 1 if c <= keys_count then gradient.conf[gradient.config_keys[c]].value = g else local kc = 20 + c - keys_count if kc % 5 == 1 then c, kc = c + 1, kc + 1 end if not gradient.conf[kc] then gradient.append_color_components() end gradient.conf[kc].value = g end end end end function gradient.append_color_components() gradient.color_comp_count = gradient.color_comp_count + 1 gradient.conf[16 + gradient.color_comp_count * 5] = { label = "Color " .. gradient.color_comp_count; class = "label"; x = 0; y = gradient.color_comp_count + 6; height = 1; width = 1 } for i = 1, 4 do gradient.conf[i + 16 + gradient.color_comp_count * 5] = { name = gradient.colorkeys[i] .. "_color" .. gradient.color_comp_count; class = "color"; x = i; y = gradient.color_comp_count + 6; height = 1; width = 1 } end end function gradient.unappend_color_components() for i = 0, 4 do gradient.conf[i + 16 + gradient.color_comp_count * 5] = nil end gradient.color_comp_count = gradient.color_comp_count - 1 end function gradient.process(meta, styles, config, subtitles, selected_lines, active_line) gradient.save_config(config) gradient.last_run = os.time() local scfg = gradient.serialize_config(config) -- Get colors local colors_count = { primary = 0; secondary = 0; outline = 0; shadow = 0 } local colors = { primary = {}; secondary = {}; outline = {}; shadow = {} } for k = 1,4 do local key = gradient.colorkeys[k] if config[key .. "_mode"] ~= "Ignore" then count, _ = string.headtail(config[key .. "_mode"]) colors_count[key] = tonumber(count) end for j = 1,colors_count[key] do local htmlcolor = config[key .. "_color" .. j] local r, g, b = string.match(htmlcolor, "(%x%x)(%x%x)(%x%x)") colors[key][j] = ass_color(tonumber(r,16), tonumber(g,16), tonumber(b,16)) end end

if colors_count["primary"] + colors_count["secondary"]+ colors_count["outline"] + colors_count["shadow"] == 0 then aegisub.debug.out(0, "Operation failed because you did not configure gradient count for primary/secondary/outline/shadow colors.") return false end

-- Mode local mode_digest = { ["Smooth"] = 1; ["Smooth (Vertical)"] = 2; ["By character"] = 3; ["By syllable"] = 4 } config.mode = mode_digest[config.mode]

-- karaoke tag function config.karatagfn = function(syl) return "" end if config.karamode ~= "Strip karaoke" then if config.karamode == "As Is" then config.karatagfn = function(syl) return string.format("{\\%s%d}", syl.tag, syl.kdur) end else config.karatagfn = function(syl) return string.format("{%s%d}", config.karamode, syl.kdur) end end end

-- Get lines indexes local subtitles2 = {} local subs = {} local applyto_type, applyto_more = string.headtail(config.applyto) if applyto_type == "All" then for i = 1, #subtitles do if subtitles[i].class == "dialogue" and not subtitles[i].comment and not gradient.has_gradient(subtitles[i]) then table.insert(subs,i) end end elseif applyto_type == "Selected" then subs = selected_lines elseif applyto_type == "Style" then local _, applytostyle = string.headtail(applyto_more) for i = 1, #subtitles do if subtitles[i].class == "dialogue" and not subtitles[i].comment and not gradient.has_gradient(subtitles[i]) and subtitles[i].style == applytostyle then table.insert(subs,i) end end elseif applyto_type == "Actor" then local _, applytoactor = string.headtail(applyto_more) for i = 1, #subtitles do if subtitles[i].class == "dialogue" and not subtitles[i].comment and not gradient.has_gradient(subtitles[i]) and subtitles[i].actor == applytoactor then table.insert(subs,i) end end end -- process them local lasti = 0 local count = 0 local newlines = 0 local configstored = false for _, i in pairs(subs) do count = count + 1 aegisub.progress.set(count * 100 / #subs) if aegisub.progress.is_cancelled() then return false end for j = lasti + 1, i - 1 do if not configstored then if subtitles[j].class == "clear" then local iline = { class = "info", section = "Script Info"; key = "GradientFactory"; value = scfg } table.insert(subtitles2, iline) configstored = true elseif subtitles[j].class == "info" and subtitles[j].key == "GradientFactory" then local iline = table.copy(subtitles[j]) iline.value = scfg subtitles[j] = iline configstored = true end end table.insert(subtitles2, subtitles[j]) end local line = subtitles[i] karaskel.preproc_line(subtitles, meta, styles, line) local res = gradient.do_line(meta, styles, config, colors, line) for j = 1, #res do table.insert(subtitles2, res[j]) newlines = newlines + 1 end lasti = i aegisub.progress.task(count .. " / " .. #subs .. " lines processed") end for j = lasti + 1, #subtitles do table.insert(subtitles2, subtitles[j]) end -- clear subtitles and rebuild subtitles.deleterange(1, #subtitles) --subtitles.delete(1) for j = 1, #subtitles2 do subtitles[0] = subtitles2[j] end

return true end function gradient.do_line(meta, styles, config, colors, line) local results = {} local linetext = "" local nline = {} local mode = config.mode if #line.kara == 0 and mode == 4 then mode = 3 end local linewidth = line.width + 2*line.styleref.outline + line.styleref.shadow local lineheight = line.height + 2*line.styleref.outline + line.styleref.shadow local lineleft = line.left - line.styleref.outline local lineright = line.right + line.styleref.outline + line.styleref.shadow local linetop = line.top - line.styleref.outline local linebottom = line.bottom + line.styleref.outline + line.styleref.shadow math.randomseed(os.time()+line.start_time) local randomtag = math.random(273, 4095)

-- new in 1.2: preserve \pos and \move local pos_mode, pos_tag = 0, string.format("\\pos(%d,%d)", line.x, line.y) local dx, dy, dy1, dy2, animtimes = 0, 0, 0, 0, "" local pos_s, _, pos_x, pos_y = string.find(line.text, "{[^}]*\\pos%(([^,%)]*),([^,%)]*)%).*}") local mov_s, _, mov_x1, mov_y1, mov_x2, mov_y2 = string.find(line.text, "{[^}]*\\move%(([^,%)]*),([^,%)]*),([^,%)]*),([^,%)]*)%).*}") local movt_s, _, movt_x1, movt_y1, movt_x2, movt_y2, movt_t1, movt_t2 = string.find(line.text, "{[^}]*\\move%(([^,%)]*),([^,%)]*),([^,%)]*),([^,%)]*),([^,%)]*),([^,%)]*)%).*}") local coordparse = function(h) local i = string.headtail(string.trim(tostring(h))); i = tonumber(i); if not i then i = 0 end; return i end if pos_s and (not mov_s or pos_s < mov_s) and (not movt_s or pos_s < movt_s) then pos_mode = 1 pos_x, pos_y = coordparse(pos_x), coordparse(pos_y) pos_tag = string.format("\\pos(%d,%d)", pos_x, pos_y) dx, dy = pos_x - line.x, pos_y - line.y elseif mov_s and (not movt_s or mov_s < movt_s) then pos_mode = 2 mov_x1, mov_y1, mov_x2, mov_y2 = coordparse(mov_x1), coordparse(mov_y1), coordparse(mov_x2), coordparse(mov_y2) pos_tag = string.format("\\move(%d,%d,%d,%d)", mov_x1, mov_y1, mov_x2, mov_y2) dx, dy = mov_x1 - line.x, mov_y1 - line.y dx2, dy2 = mov_x2 - line.x, mov_y2 - line.y elseif movt_s then pos_mode = 3 movt_x1, movt_y1, movt_x2, movt_y2 = coordparse(movt_x1), coordparse(movt_y1), coordparse(movt_x2), coordparse(movt_y2) movt_t1, movt_t2 = coordparse(movt_t1), coordparse(movt_t2) pos_tag = string.format("\\move(%d,%d,%d,%d,%d,%d)", movt_x1, movt_y1, movt_x2, movt_y2, movt_t1, movt_t2) dx, dy = movt_x1 - line.x, movt_y1 - line.y dx2, dy2 = movt_x2 - line.x, movt_y2 - line.y animtimes = string.format("%d,%d,",movt_t1,movt_t2) end local clipper = function(x1, y1, x2, y2) local outstr = string.format("\\clip(%d,%d,%d,%d)", x1+dx, y1+dy, x2+dx, y2+dy) if pos_mode > 1 then outstr = outstr .. string.format("\\t(%s\\clip(%d,%d,%d,%d))", animtimes, x1+dx2, y1+dy2, x2+dx2, y2+dy2) end return outstr .. pos_tag end if mode < 3 then nline = table.copy(line) nline.comment = true nline.effect = string.format("gradient @%x 0", randomtag) results = { [1] = nline } if #line.kara > 0 then for s, syl in ipairs(line.kara) do linetext = linetext .. config.karatagfn(syl) .. syl.text_stripped end else linetext = line.text_stripped end end if mode == 1 then local left, right = 0, config.stripx local count = 0 local nlinewidth = linewidth-config.stripx repeat nline = table.copy(line) nlinetext = string.format("{%s%s}%s", clipper(left+lineleft,linetop,right+lineleft,linebottom), gradient.color_interpolator(left, nlinewidth, colors), linetext) nline.text = nlinetext count = count + 1 nline.effect = string.format("gradient @%x %00d", randomtag, count) table.insert(results, nline) left = right right = right + config.stripx until left >= linewidth and not aegisub.progress.is_cancelled() elseif mode == 2 then local top, bottom = 0, config.stripx local count = 0 local nlineheight = lineheight-config.stripx repeat nline = table.copy(line) nlinetext = string.format("{%s%s}%s", clipper(lineleft,linetop+top,lineright,linetop+bottom), gradient.color_interpolator(top, nlineheight, colors), linetext) nline.text = nlinetext count = count + 1 nline.effect = string.format("gradient @%x %00d", randomtag, count) table.insert(results, nline) top = bottom bottom = bottom + config.stripx until top >= lineheight and not aegisub.progress.is_cancelled() elseif mode == 3 then if #line.kara > 0 and config.karamode ~= "Strip karaoke" then for s, syl in ipairs(line.kara) do local left, right, syltext = syl.left,0,"" for char in unicode.chars(syl.text_stripped) do width, height, descent, ext_lead = aegisub.text_extents(line.styleref, char) right = left + width local colortags = gradient.color_interpolator(gradient.calc_j(left, right, line.width), line.width, colors) if colortags ~= "" then colortags = "{" .. colortags .. "}" end syltext = syltext .. colortags .. char left = right end linetext = linetext .. config.karatagfn(syl) .. syltext end else local left, right = 0,0 for char in unicode.chars(line.text_stripped) do local width, height, descent, ext_lead = aegisub.text_extents(line.styleref, char) right = left + width local colortags = gradient.color_interpolator(gradient.calc_j(left, right, line.width), line.width, colors) if colortags ~= "" then colortags = "{" .. colortags .. "}" end linetext = linetext .. colortags .. char left = right end end if pos_mode > 0 then linetext = string.format("{%s}%s", pos_tag, linetext) end elseif mode == 4 then for s, syl in ipairs(line.kara) do local colortags = gradient.color_interpolator(gradient.calc_j(syl.left, syl.right, line.width), line.width, colors) if colortags ~= "" then colortags = "{" .. colortags .. "}" end local syltext = config.karatagfn(syl) .. colortags .. syl.text_stripped linetext = linetext .. syltext end if pos_mode > 0 then linetext = string.format("{%s}%s", pos_tag, linetext) end end

if mode > 2 then nline = table.copy(line) nline.text = linetext results = { [1] = nline } end

return results end function gradient.calc_j(left, right, width) if left + right < width then return left + ((right - left) * left / width) else return left + ((right - left) * right / width) end end function gradient.color_interpolator(j, maxj, colors) local colors_out = "" for c = 1,4 do local dcolors = colors[gradient.colorkeys[c]] local cc = #dcolors if cc > 1 then local nmaxj = maxj/(cc-1) local k = clamp(math.floor(j/nmaxj), 0, cc-2) local nj = j - (k*nmaxj) colors_out = colors_out .. string.format("\\%dc%s",c,interpolate_color(nj/nmaxj, dcolors[k+1], dcolors[k+2])) end end return colors_out end function gradient.prepareconfig(styles, subtitles, selected) local applyto = 4 gradient.conf[applyto].items = {} oldapplytovalue = gradient.conf[applyto].value gradient.conf[applyto].value = "All lines" table.insert(gradient.conf[applyto].items, gradient.conf[applyto].value) if #selected > 0 then applytoselected = string.format("Selected lines (%d)", #selected) table.insert(gradient.conf[applyto].items, applytoselected) if oldapplytovalue == applytoselected then gradient.conf[applyto].value = applytoselected end end for i, style in ipairs(styles) do itemname = string.format("Style = %s", style.name) table.insert(gradient.conf[applyto].items, itemname) if oldapplytovalue == itemname then gradient.conf[applyto].value = itemname end end local actors = {} for i = 1, #subtitles do if subtitles[i].class == "dialogue" and not subtitles[i].comment and subtitles[i].actor ~= "" then if not actors[subtitles[i].actor] then actors[subtitles[i].actor] = true itemname = string.format("Actor = %s", subtitles[i].actor) table.insert(gradient.conf[applyto].items, itemname) if oldapplytovalue == itemname then gradient.conf[applyto].value = itemname end end end end for i = 1, 4 do local j = 11 + i * 2 local found, item = (gradient.conf[j].value == "Ignore"), "" gradient.conf[j].items = { [1] = "Ignore" } for k = 2, gradient.color_comp_count do item = string.format("%d colors", k) gradient.conf[j].items[k] = item if gradient.conf[j].value == item then found = true end end if not found then gradient.conf[j].value = item end end end function gradient.macro_process(subtitles, selected_lines, active_line) local meta, styles = karaskel.collect_head(subtitles) -- configuration if meta["gradientfactory"] ~= nil then gradient.unserialize_config(meta["gradientfactory"]) end

-- filter selected_lines local subs = {} for _, i in ipairs(selected_lines) do if not subtitles[i].comment and not gradient.has_gradient(subtitles[i]) then table.insert(subs,i) end end selected_lines = subs

-- display dialog local cfg_res, config repeat gradient.prepareconfig(styles, subtitles, selected_lines) local dlgbuttons = {"Generate","+colors","-colors","Cancel"} if gradient.color_comp_count <= 2 then dlgbuttons = {"Generate","+colors","Cancel"} end cfg_res, config = aegisub.dialog.display(gradient.conf, dlgbuttons) if cfg_res == "+colors" then gradient.save_config(config) gradient.append_color_components() elseif cfg_res == "-colors" then gradient.save_config(config) gradient.unappend_color_components() end until cfg_res ~= "+colors" and cfg_res ~= "-colors"

if cfg_res == "Generate" then result = gradient.process(meta, styles, config, subtitles, selected_lines, active_line) if result then aegisub.set_undo_point("Generate color gradient") aegisub.progress.task("Done") else aegisub.progress.task("Failed"); end else aegisub.progress.task("Cancelled"); end end function gradient.macro_undo(subtitles, selected_lines, active_line) local tag = string.match(subtitles[selected_lines[1]].effect, "@%x+") local pattern = "^gradient " .. tag .. " (%d+)$" local subtitles2 = {}

for i = 1, #subtitles do local nline = table.copy(subtitles[i]) if subtitles[i].class == "dialogue" then local c = string.match(subtitles[i].effect, pattern) if c == "0" then nline.comment = false nline.effect = "" table.insert(subtitles2, nline) elseif c == nil then table.insert(subtitles2, nline) end else table.insert(subtitles2, nline) end end

subtitles.deleterange(1, #subtitles) --subtitles.delete(1) for j = 1, #subtitles2 do subtitles[0] = subtitles2[j] end

aegisub.set_undo_point("Un-gradient") end function gradient.validate_undo(subtitles, selected_lines, active_line) if not (#selected_lines > 0) then return false end return gradient.has_gradient(subtitles[selected_lines[1]]) end function gradient.has_gradient(line) return (nil ~= string.match(line.effect, "^gradient @%x+ %d+$")) end

-- register macros aegisub.register_macro("Generate color gradient", "Generate color gradient", gradient.macro_process) aegisub.register_macro("Un-gradient", "Un-gradient", gradient.macro_undo, gradient.validate_undo) --[[ ==README==

Frame-by-Frame Transform Automation Script

Smoothly transforms various parameters across multi-line, frame-by-frame typesets.

Useful for adding smooth transitions to frame-by-frame typesets that cannot be tracked with mocha, or as a substitute for the stepped \t transforms generated by the Aegisub-Motion.lua script, which may cause more lag than hard-coded values.

First generate the frame-by-frame typeset that you will be adding the effect to. Find the lines where you want the effect to begin and the effect to end, and visually typeset them until they look the way you want them to.

These lines will act as "keyframes", and the automation will modify all the lines in between so that the appearance of the first line smoothly transitions into the appearance of the last line. Simply highlight the first line, the last line, and all the lines in between, and run the automation.

It will only affect the tags that are checked in the popup menu when you run the automation. To uncheck all, press the "clear" button.

This may be obvious, but this automation only works on one layer or one component of a frame-by-frame typeset at a time. If you have a frame-by-frame typeset that has two lines per frame, which looks like:

A1 B1 A2 B2 A3 B3 etc.

Then this automation will not work. The lines must be organized as:

A1 A2 A3 etc. B1 B2 B3 etc.

And you would have to run the automation twice, once on A and once on B. Furthermore, the text of each line must be exactly the same once all tags are removed. You can have as many tag blocks as you want in whatever positions you want for the "keyframe" lines (the first and the last). But once the tags are taken out, the text of the lines must be identical, down to the last space. If you are using ctrl-D or copy-pasting, this should be a given, but it's worth a warning.

The lines in between can have any tags you want in them. So long as the automation is not transforming those particular tags, they will be left untouched. If you need the typeset to suddenly turn huge for one frame, simply uncheck "fscx" and "fscy" when you run the automation, and the size of the line won't be touched.

Comes with an extra automation "Remove tags" that utilizes functions that were written for the main automation. You can comment out (two dashes) the line at the bottom that adds this automation if you don't want it.

TODO: clean up code, make modular mass mocha tag modifier automation

]]-- script_name="Frame-by-frame transform" script_description="Smoothly transforms between the first and last selected lines." script_version="0.6" include("karaskel.lua") include("utils.lua")

--Lookup table for the nature of each kind of parameter param_type={ ["alpha"] = "alpha", ["1a"] = "alpha", ["2a"] = "alpha", ["3a"] = "alpha", ["4a"] = "alpha", ["c"] = "color", ["1c"] = "color", ["2c"] = "color", ["3c"] = "color", ["4c"] = "color", ["fscx"] = "number", ["fscy"] = "number", ["frz"] = "angle", ["frx"] = "angle", ["fry"] = "angle", ["shad"] = "number", ["bord"] = "number", ["fsp"] = "number", ["fs"] = "number", ["fax"] = "number", ["fay"] = "number", ["blur"] = "number", ["be"] = "number", ["xbord"] = "number", ["ybord"] = "number", ["xshad"] = "number", ["yshad"] = "number" } function create_config() local config={ --first the colors { class="checkbox", name="c",label="c", x=0, y=0, width=1, height=1, value=true }, { class="checkbox", name="2c",label="2c", x=1, y=0, width=1, height=1, value=true }, { class="checkbox", name="3c",label="3c", x=2, y=0, width=1, height=1, value=true }, { class="checkbox", name="4c",label="4c", x=3, y=0, width=1, height=1, value=true }, --then the alphas { class="checkbox", name="alpha",label="alpha", x=0, y=1, width=1, height=1, value=true }, { class="checkbox", name="1a",label="1a", x=1, y=1, width=1, height=1, value=true }, { class="checkbox", name="2a",label="2a", x=2, y=1, width=1, height=1, value=true }, { class="checkbox", name="3a",label="3a", x=3, y=1, width=1, height=1, value=true }, { class="checkbox", name="4a",label="4a", x=4, y=1, width=1, height=1, value=true }, --scale { class="checkbox", name="fscx",label="fscx", x=0, y=2, width=1, height=1, value=true }, { class="checkbox", name="fscy",label="fscy", x=1, y=2, width=1, height=1, value=true }, --shear { class="checkbox", name="fax",label="fax", x=2, y=2, width=1, height=1, value=true }, { class="checkbox", name="fay",label="fay", x=3, y=2, width=1, height=1, value=true }, --rotation { class="checkbox", name="frx",label="frx", x=0, y=3, width=1, height=1, value=true }, { class="checkbox", name="fry",label="fry", x=1, y=3, width=1, height=1, value=true }, { class="checkbox", name="frz",label="frz", x=2, y=3, width=1, height=1, value=true }, --border, shadow, font size, font spacing { class="checkbox", name="bord",label="bord", x=0, y=4, width=1, height=1, value=true }, { class="checkbox", name="shad",label="shad", x=1, y=4, width=1, height=1, value=true }, { class="checkbox", name="fs",label="fs", x=2, y=4, width=1, height=1, value=true }, { class="checkbox", name="fsp",label="fsp", x=3, y=4, width=1, height=1, value=true }, --x/y bord/shad { class="checkbox", name="xbord",label="xbord", x=0, y=5, width=1, height=1, value=true }, { class="checkbox", name="ybord",label="ybord", x=1, y=5, width=1, height=1, value=true }, { class="checkbox", name="xshad",label="xshad", x=2, y=5, width=1, height=1, value=true }, { class="checkbox", name="yshad",label="yshad", x=3, y=5, width=1, height=1, value=true }, --blur { class="checkbox", name="blur",label="blur", x=0, y=6, width=1, height=1, value=true }, { class="checkbox", name="be",label="be", x=1, y=6, width=1, height=1, value=true }, --Pos, org, clip { class="checkbox", name="do_pos",label="pos", x=0,y=7,wdith=1,height=1, value=false }, { class="checkbox", name="do_org",label="org", x=1,y=7,wdith=1,height=1, value=false }, { class="checkbox", name="do_clip",label="clip", x=2,y=7,wdith=1,height=1, value=true }, --Flip rotation { class="checkbox", name="flip_rot",label="Flip rotation direction", x=0,y=8,width=4,height=1, value=true }, --Acceleration { class="label", label="Acceleration:", x=0,y=9,width=2,height=1 }, { class="floatedit", name="accel", x=2,y=9,width=3,height=1, value=1.0, hint="1 means no acceleration, >1 starts slow and ends fast, <1 starts fast and ends slow" } }

return config end

--Convert float to neatly formatted string local function float2str( f ) return string.format("%.2f",f):gsub("%.(%d-)0+$","%.%1"):gsub("%.$","") end

--[[ Tags that can have any character after the tag declaration: \r \fn Otherwise, the first character after the tag declaration must be: a number, decimal point, open parentheses, minus sign, or ampersand ]]--

--Remove listed tags from the given text local function line_exclude(text, exclude) remove_t=false text=text:gsub("\\([^\\{}]*)", function(a) if a:find("^r")~=nil then for i,val in ipairs(exclude) do if val=="r" then return "" end end elseif a:find("^fn")~=nil then for i,val in ipairs(exclude) do if val=="fn" then return "" end end else _,_,tag=a:find("^([1-4]?%a+)") for i,val in ipairs(exclude) do if val==tag then --Hacky exception handling for \t statements if val=="t" then remove_t=true return "\\"..a end return "" end end end return "\\"..a end) if remove_t then text=text:gsub("\\t%b()","") end return text end

--Remove all tags except the given ones local function line_exclude_except(text, exclude) remove_t=true text=text:gsub("\\([^\\{}]*)", function(a) if a:find("^r")~=nil then for i,val in ipairs(exclude) do if val=="r" then return "\\"..a end end elseif a:find("^fn")~=nil then for i,val in ipairs(exclude) do if val=="fn" then return "\\"..a end end else _,_,tag=a:find("^([1-4]?%a+)") for i,val in ipairs(exclude) do if val==tag then if val=="t" then remove_t=false end return "\\"..a end end end return "" end) if remove_t then text=text:gsub("\\t%b()","") end return text end

--Remove listed tags from any \t functions in the text local function time_exclude(text,exclude) text=text:gsub("(\\t%b())", function(a) b=a for y=1,#exclude,1 do if(string.find(a,"\\"..exclude[y])~=nil) then if exclude[y]=="clip" then b=b:gsub("\\"..exclude[y].."%b()","") else b=b:gsub("\\"..exclude[y].."[^\\%)]*","") end end end return b end ) --get rid of empty blocks text=text:gsub("\\t%([%-%.%d,]*%)","") return text end

--Returns a table of default values local function style_lookup(line) local style_table={ ["alpha"] = alpha_from_style(line.styleref.color1), ["1a"] = alpha_from_style(line.styleref.color1), ["2a"] = alpha_from_style(line.styleref.color2), ["3a"] = alpha_from_style(line.styleref.color3), ["4a"] = alpha_from_style(line.styleref.color4), ["c"] = color_from_style(line.styleref.color1), ["1c"] = color_from_style(line.styleref.color1), ["2c"] = color_from_style(line.styleref.color2), ["3c"] = color_from_style(line.styleref.color3), ["4c"] = color_from_style(line.styleref.color4), ["fscx"] = line.styleref.scale_x, ["fscy"] = line.styleref.scale_y, ["frz"] = line.styleref.angle, ["frx"] = 0, ["fry"] = 0, ["shad"] = line.styleref.shadow, ["bord"] = line.styleref.outline, ["fsp"] = line.styleref.spacing, ["fs"] = line.styleref.fontsize, ["fax"] = 0, ["fay"] = 0, ["xbord"] = line.styleref.outline, ["ybord"] = line.styleref.outline, ["xshad"] = line.styleref.shadow, ["yshad"] = line.styleref.shadow, ["blur"] = 0, ["be"] = 0 } return style_table end

--The main body of code that runs the frame transform function frame_transform(sub,sel,config)

--Get meta and style info local meta,styles = karaskel.collect_head(sub, false)

--Set the first and last lines in the selection local first_line=sub[sel[1]]; local last_line=sub[sel[#sel]];

--Preprocess lines karaskel.preproc_line(sub,meta,styles,first_line) karaskel.preproc_line(sub,meta,styles,last_line)

--These are the tags to transform transform_tags={}

--Add based on config --(This could probably be done with a for statement, but it's not like that'll have better runtime) if config["c"] then table.insert(transform_tags,"c") end if config["2c"] then table.insert(transform_tags,"2c") end if config["3c"] then table.insert(transform_tags,"3c") end if config["4c"] then table.insert(transform_tags,"4c") end if config["alpha"] then table.insert(transform_tags,"alpha") end if config["1a"] then table.insert(transform_tags,"1a") end if config["2a"] then table.insert(transform_tags,"2a") end if config["3a"] then table.insert(transform_tags,"3a") end if config["4a"] then table.insert(transform_tags,"4a") end if config["fscx"] then table.insert(transform_tags,"fscx") end if config["fscy"] then table.insert(transform_tags,"fscy") end if config["frx"] then table.insert(transform_tags,"frx") end if config["fry"] then table.insert(transform_tags,"fry") end if config["frz"] then table.insert(transform_tags,"frz") end if config["bord"] then table.insert(transform_tags,"bord") end if config["shad"] then table.insert(transform_tags,"shad") end if config["fsp"] then table.insert(transform_tags,"fsp") end if config["fs"] then table.insert(transform_tags,"fs") end if config["blur"] then table.insert(transform_tags,"blur") end if config["be"] then table.insert(transform_tags,"be") end if config["fax"] then table.insert(transform_tags,"fax") end if config["fay"] then table.insert(transform_tags,"fay") end if config["xbord"] then table.insert(transform_tags,"xbord") end if config["ybord"] then table.insert(transform_tags,"ybord") end if config["xshad"] then table.insert(transform_tags,"xshad") end if config["yshad"] then table.insert(transform_tags,"yshad") end

--Controls whether rotations over 180 degrees are calculated as negative do_flip_rotation=config["flip_rot"]

--Set the acceleration (default 1) local accel=config["accel"]

--[[ TESTING SECTION TESTING SECTION ]]--

--Controls whether to apply transform to pos, clip, or origin do_clip=config["do_clip"] do_org=config["do_org"] do_pos=config["do_pos"] local _,_,sposx,sposy=first_line.text:find("\\pos%(([%d%.%-]*),([%d%.%-]*)%)") local _,_,eposx,eposy=last_line.text:find("\\pos%(([%d%.%-]*),([%d%.%-]*)%)") if sposx==nil then _,_,sposx,sposy=first_line.text:find("\\move%(([%d%.%-]*),([%d%.%-]*),") if sposx==nil then sposx=first_line.x sposy=first_line.y end end if eposx==nil then _,_,eposx,eposy=last_line.text:find("\\move%(([%d%.%-]*),([%d%.%-]*),") if eposx==nil then eposx=last_line.x eposy=last_line.y end end

--Look for origin local _,_,sorgx,sorgy=first_line.text:find("\\org%(([%d%.%-]*),([%d%.%-]*)%)") local _,_,eorgx,eorgy=last_line.text:find("\\org%(([%d%.%-]*),([%d%.%-]*)%)") if sorgx==nil then sorgx=first_line.x sorgy=first_line.y end if eorgx==nil then eorgx=last_line.x eorgy=last_line.y end

--Look for clips local _,_,sclip1,sclip2,sclip3,sclip4= first_line.text:find("\\clip%(([%d%.%-]*),([%d%.%-]*),([%d%.%-]*),([%d%.%-]*)%)") local _,_,eclip1,eclip2,eclip3,eclip4= last_line.text:find("\\clip%(([%d%.%-]*),([%d%.%-]*),([%d%.%-]*),([%d%.%-]*)%)")

--If either the first or last line do not contain a rectangular clip, you will not be clipping today if sclip1==nil or eclip1==nil then do_clip=false end --[[ END TESTING SECTION END TESTING SECTION ]]--

--Make sure each line starts with tags if first_line.text:find("^{")==nil then first_line.text="{}"..first_line.text end if last_line.text:find("^{")==nil then last_line.text="{}"..last_line.text end

--Turn all \1c tags into \c tags, just for convenience first_line.text=first_line.text:gsub("\\1c","\\c") last_line.text=last_line.text:gsub("\\1c","\\c")

--The tables that store the line as objects consisting of a tag and the text that follows it local start_table={} local end_table={}

--Separate each line into a table of tags and text x=1 for thistag,thistext in first_line.text:gmatch("({[^{}]*})([^{}]*)") do start_table[x]={tag=thistag,text=thistext} x=x+1 end x=1 for thistag,thistext in last_line.text:gmatch("({[^{}]*})([^{}]*)") do end_table[x]={tag=thistag,text=thistext} x=x+1 end

--Make sure both lines have the same splits local i=1 while(i<=#start_table) do stext=start_table[i].text stag=start_table[i].tag etext=end_table[i].text etag=end_table[i].tag --If the start table item has longer text, break it in two based on the text of the end table if stext:len() > etext:len() then _,_,newtext=stext:find(etext.."(.*)") for j=#start_table,i+1,-1 do start_table[j+1]=start_table[j] end start_table[i]={tag=stag,text=etext} start_table[i+1]={tag="{}",text=newtext} --If the end table item has longer text, break it in two based on the text of the start table elseif stext:len() < etext:len() then _,_,newtext=etext:find(stext.."(.*)") for j=#end_table,i+1,-1 do end_table[j+1]=end_table[j] end end_table[i]={tag=etag,text=stext} end_table[i+1]={tag="{}",text=newtext} end

i=i+1 end

--Tables that store tables for each tag block, consisting of the state of all relevant tags --that are in the transform_tags table local start_state_table={} local end_state_table={}

--Calculate state of each tag block for i,val in ipairs(start_table) do temp_start_table={} pstate=line_exclude_except(val.tag,transform_tags) for j,ctag in ipairs(transform_tags) do --param MUST start in a non-alpha character, because ctag will never be \r or \fn --If it is, you fucked up _,_,param=pstate:find("\\"..ctag.."(%A[^\\{}]*)") if param~=nil then temp_start_table[ctag]=param end end start_state_table[i]=temp_start_table end for i,val in ipairs(end_table) do temp_end_table={} pstate=line_exclude_except(val.tag,transform_tags) for j,ctag in ipairs(transform_tags) do --param MUST start in a non-alpha character, because ctag will never be \r or \fn --If it is, you fucked up _,_,param=pstate:find("\\"..ctag.."(%A[^\\{}]*)") if param~=nil then temp_end_table[ctag]=param end end end_state_table[i]=temp_end_table end

--Insert default values when not included for the state of each tag block, --or inherit values from previous tag block start_style=style_lookup(first_line) end_style=style_lookup(last_line) current_end_state={} current_start_state={} for i,sval in ipairs(start_state_table) do --build current state tables for skey,sparam in pairs(sval) do current_start_state[skey]=sparam end for ekey,eparam in pairs(end_state_table[i]) do current_end_state[ekey]=eparam end

--check if end is missing any tags that start has for skey,sparam in pairs(sval) do if end_state_table[i][skey]==nil then if current_end_state[skey]==nil then end_state_table[i][skey]=end_style[skey] else end_state_table[i][skey]=current_end_state[skey] end end end --check if start is missing any tags that end has for ekey,eparam in pairs(end_state_table[i]) do if start_state_table[i][ekey]==nil then if current_start_state[ekey]==nil then start_state_table[i][ekey]=start_style[ekey] else start_state_table[i][ekey]=current_start_state[ekey] end end end end

--[[for i,sval in ipairs(start_state_table) do aegisub.debug.out(2,tostring(i).."th tag block\n") for skey,sparam in pairs(sval) do aegisub.debug.out(2," Tag: "..skey.." Value: "..sparam.."\n") end end]]--

--Insert proper state into each intervening line for i=2,#sel-1,1 do this_line=sub[sel[i]]

--Remove all the relevant tags so they can be replaced with their proper interpolated values this_line.text=time_exclude(this_line.text,transform_tags) this_line.text=line_exclude(this_line.text,transform_tags) this_line.text=this_line.text:gsub("{}","")

--Make sure this line starts with tags if this_line.text:find("^{")==nil then this_line.text="{}"..first_line.text end

--The interpolation factor for this particular line local factor=((i-1)^accel)/((#sel-1)^accel)

--Handle pos transform if do_pos then this_line.text=line_exclude(this_line.text,{"pos"}) this_line.text=this_line.text:gsub("^{", string.format("{\\pos(%.2f,%.2f)", interpolate(factor,sposx,eposx), interpolate(factor,sposy,eposy) ) ) end

--Handle org transform if do_org then this_line.text=line_exclude(this_line.text,{"org"}) this_line.text=this_line.text:gsub("^{", string.format("{\\org(%.2f,%.2f)", interpolate(factor,sorgx,eorgx), interpolate(factor,sorgy,eorgy) ) ) end

--Handle clip transform if do_clip then this_line.text=line_exclude(this_line.text,{"clip"}) this_line.text=this_line.text:gsub("^{", string.format("{\\clip(%d,%d,%d,%d)", interpolate(factor,sclip1,eclip1), interpolate(factor,sclip2,eclip2), interpolate(factor,sclip3,eclip3), interpolate(factor,sclip4,eclip4) ) ) end

--Break the line into a table local this_table={} x=1 for thistag,thistext in this_line.text:gmatch("({[^{}]*})([^{}]*)") do this_table[x]={tag=thistag,text=thistext} x=x+1 end

--Make sure it has the same splits local j=1 while (j<=#start_table) do stext=start_table[j].text stag=start_table[j].tag ttext=this_table[j].text ttag=this_table[j].tag

--ttext might contain miscellaneous tags that are not being checked for, so remove them temporarily ttext_temp=ttext:gsub("{[^{}]*}","") --If this table item has longer text, break it in two based on the text of the start table if ttext_temp:len() > stext:len() then _,_,newtext=ttext_temp:find(stext.."(.*)") for j=#this_table,j+1,-1 do this_table[j+1]=this_table[j] end this_table[j]={tag=ttag,text=ttext:gsub(newtext.."$","")} this_table[j+1]={tag="{}",text=newtext} end --If the start table has longer text, then perhaps ttext was split at a tag that's not being transformed if ttext:len() < stext:len() then --It should be impossible for this to happen at the end, but check anyway if this_table[j+1]~=nil then this_table[j].text=ttext..this_table[j+1].tag..this_table[j+1].text if this_table[j+2]~=nil then for j=j+1,#this_table-1,1 do this_table[j]=this_table[j+1] end end this_table[#this_table]=nil j=j-1 else aegisub.log("You fucked up big time somewhere. Sorry.") return end end j=j+1 end

--Interpolate all the relevant parameters and insert rebuilt_text="" this_current_state={} for k,val in ipairs(this_table) do temp_tag=val.tag --Cycle through all the tag blocks and interpolate for ctag,param in pairs(start_state_table[k]) do if param_type[ctag]=="alpha" then temp_tag=temp_tag:gsub("}", function() --aegisub.debug.out(2," interpolating "..ctag.."\n") avalue=interpolate_alpha(factor,start_state_table[k][ctag],end_state_table[k][ctag])

--check for redundancy if this_current_state[ctag]~=nil and this_current_state[ctag]==avalue then return "}" end this_current_state[ctag]=avalue

return "\\"..ctag..avalue.."}" end) elseif param_type[ctag]=="color" then temp_tag=temp_tag:gsub("}", function() --aegisub.debug.out(2," interpolating "..ctag.."\n") cvalue=interpolate_color(factor,start_state_table[k][ctag],end_state_table[k][ctag])

--check for redundancy if this_current_state[ctag]~=nil and this_current_state[ctag]==cvalue then return "}" end this_current_state[ctag]=cvalue

return "\\"..ctag..cvalue.."}" end) elseif param_type[ctag]=="number" or param_type[ctag]=="angle" then temp_tag=temp_tag:gsub("}", function() --aegisub.debug.out(2," interpolating "..ctag.."\n") nstart=tonumber(start_state_table[k][ctag]) nend=tonumber(end_state_table[k][ctag]) if param_type[ctag]=="angle" and do_flip_rotation then if nstart>180 then nstart=nstart-360 end if nend>180 then nend=nend-360 end end nvalue=interpolate(factor,nstart,nend)

if param_type[ctag]=="angle" and nvalue<0 then nvalue=nvalue+360 end

--check for redundancy if this_current_state[ctag]~=nil and this_current_state[ctag]==nvalue then return "}" end this_current_state[ctag]=nvalue

return "\\"..ctag..float2str(nvalue).."}" end) end end rebuilt_text=rebuilt_text..temp_tag..val.text end

--Reinsert the line this_line.text=rebuilt_text:gsub("{}","") sub[sel[i]]=this_line end

--Set undo point aegisub.set_undo_point(script_name); end function load_fbf(sub,sel)

--Make sure at least 3 lines are selected if #sel<3 then aegisub.dialog.display( {{class="label",label="Please select at least three lines.",x=0,y=0,width=1,height=1}},{"OK"}) return end

local buttons={"Transform","Clear","Cancel"} local pressed,config repeat config=create_config() --Clear all checkboxes if the clear button is pressed if pressed=="Clear" then for conkey,conval in ipairs(config) do if conval.class=="checkbox" then conval.value=false end end end pressed,config=aegisub.dialog.display(config,buttons) until pressed~="Clear" if pressed=="Transform" then frame_transform(sub,sel,config) end end function load_tags_remove(sub,sel) local pressed,config=aegisub.dialog.display( { { class="label", label="Enter the tags you would like to remove (without backslashes), separated by commas", x=0,y=0,width=1,height=1 }, { class="textbox", name="tagslist",text="", x=0,y=1,width=1,height=1 }, { class="checkbox", label="Remove all EXCEPT",name="doexcept", value=false, x=0,y=2,width=1,height=1 } }, {"Remove","Cancel"} ) if pressed=="Remove" then tag_list=config["tagslist"]

--Remove spaces tag_list=tag_list:gsub("%s","")

--Add a comma at the end just for parsing convenience if tag_list:find(",$")==nil then tag_list=tag_list.."," end

--Parse the list into a table tag_table={} for tags in tag_list:gmatch("([^,]*),") do table.insert(tag_table,tags) end

--Remove or remove except the tags in the table for si, li in ipairs(sel) do line=sub[li] if config["doexcept"] then line.text=line_exclude_except(line.text,tag_table) else line.text=line_exclude(line.text,tag_table) end sub[li]=line end end end

--Register the frame-by-frame transform macro aegisub.register_macro(script_name, script_description, load_fbf)

--Register the remove tags macro aegisub.register_macro("Remove tags", "Remove or remove all except the input tags.", load_tags_remove) script_name="Colorize" script_description="Does things with colours" script_author="unanimated" script_version="4.5" script_namespace="ua.Colorize" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="4.5.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end info={} info.colorize=[[ "Colorize letter by letter" Alternates between 2-5 colours character by character, like 121212, or 123412341234. Works for primary/border/shadow/secondary (one at a time). Comments are preserved but all shifted to the end of the line.

"Colorize by word" Colorizes by word instead of by letter.

"Don't join with other tags" will keep {initial tags}{colour} separated (ie. won't nuke the "}{"). This allows some other scripts to keep the colour as part of the "text" without initial tags.]] info.shift=[[ "Shift" Shift can be used on an already colorized line to shift the colours by one letter. You have to set the right number of colours for it to work correctly! If "shift base" is "line", then it takes the colour for the first character from the last character.

"Continuous shift line by line" If you select a bunch of the same colorized lines, this shifts the colours line by line. This kind of requires that no additional weird crap is done to the lines; otherwise malfunctioning can be expected.]] info.tunecolours=[[ "Tune colours" Loads all colours from a line into a GUI and lets you change them from there. Useful for changing colours in transforms or just tuning lines with multiple colours.

"All selected" loads all 'unique' colours from all selected lines, rather than all from each line. This is much more useful for tuning/replacing colours in a larger selection.

You can select "all/nontf/transf" to affect colours only from transforms, only those not from transforms, or all.]] info.setcolours=[[ "Set colours across whole line" This is like a preparation for a gradient by character. Select number of colours. For 3 colours, it will place one at the start, one in the middle, and one before the last character. Works for 2-10 colours and sets them evenly across the line.]] info.gradient=[[ "Gradient" Creates a gradient by character. (Uses Colorize button.) There are two modes: RGB and HSL. RGB is the standard, like lyger's GBC; HSL interpolates Hue, Saturation, and Lightness separately. Use the \c, \3c, \4c, \2c checkboxes on the right to choose which colour to gradient.

"Shortest hue" makes sure that hue is interpolated in the shorter direction. Unchecking it will give you a different gradient in 50% cases.

"Double HSL gradient" will make an extra round through Hue. Note that neither of these 2 options applies to RGB.

"Use asterisks" places asterisks like lyger's GBC so that you can ungradient the line with his script.

You can use acceleration if you type it in Effect, in the following form: "accel1.5"

There are several differences from lyger's GBC: - RGB / HSL option - You can choose which types of colour you want to gradient - Other tags don't interfere with the colour gradients - You can use acceleration]] info.reverse=[[ "Reverse gradient" On the right, select types of colours to apply this to. For each colour type, colours from all tags in the line outside transforms are collected and returned in the opposite direction. A full gradient gets thus reversed. (This is separate from the Gradient function, so no need to check that.)]] info.match=[[ "Match Colours" This should apply to all colour tags in the line. c -> 3c: outline colour is changed to match primary 3c -> c: primary colour is changed to match outline c -> 4c: shadow colour is changed to match primary 3c -> 4c: shadow colour is changed to match outline c <-> 3c: primary and outline are switched Invert: all colours are inverted (red->green, yellow->blue, black->white)]] info.RGBHSL=[[ "Adjust RGB / HSL" Adjusts Red/Green/Blue or Hue/Saturation/Lightness. This works for lines with multiple same-type colour tags, including gradient by character. You can select from -255 to 255. Check types of colours you want it to apply to. "Apply to missing" means it will be applied to the colour set in style if there's no tag in the line. "Randomize" - if you set Lightness (or any RGB/HSL) to 20, the resulting colour will have anywhere between -20 and +20 of the original Lightness.]] info.general=[[ "Remember last" - Remembers last settings of checkboxes and dropdown menus.

"Repeat last" - Repeat the function with last settings.

"Save config" - Saves a config file in your Application Data folder with current settings.

"Colorize" functions: if more selected, the one lowest in the GUI is run.

Full manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#colorize ]] re=require'aegisub.re'

-- Colour Ice -- function colors(subs,sel) local c={} for k=1,5 do c[k]=res["c"..k]:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") end for z,i in ipairs(sel) do progress("Colorizing line "..z.."/"..#sel) line=subs[i] text=line.text text=text:gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end)

if res.kol=="primary" then k="\\c" text=text:gsub("\\1?c&H%x+&","") end if res.kol=="border" then k="\\3c" text=text:gsub("\\3c&H%x+&","") end if res.kol=="shadow" then k="\\4c" text=text:gsub("\\4c&H%x+&","") end if res.kol=="secondary" then k="\\2c" text=text:gsub("\\2c&H%x+&","") end

k1=k..c[1] k2=k..c[2] k3=k..c[3] k4=k..c[4] k5=k..c[5]

tags=text:match(STAG) or "" orig=text:gsub(STAG,"") comm="" for c in text:gmatch("{[^\\}]*}") do comm=comm..c end text=text:gsub("%b{}","") :gsub("%s*$","")

if res.clrs=="2" then if res.word then text=text.." * " text=re.sub(text,"([\\w[:punct:]]+) ([\\w[:punct:]]+) ","{\\"..k1.."}\\1 {\\"..k2.."}\\2 ") else text=text:gsub("%s"," ") text=text.."*" text=re.sub(text,"([\\w[:punct:]\\s])([\\w[:punct:]\\s])","{\\"..k1.."}\\1{\\"..k2.."}\\2") text=text:gsub("{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s"," ") end end

if res.clrs=="3" then if res.word then text=text.." * * " text=re.sub(text,"([\\w[:punct:]]+) ([\\w[:punct:]]+) ([\\w[:punct:]]+) ","{\\"..k1.."}\\1 {\\"..k2.."}\\2 {\\"..k3.."}\\3 ") else text=text:gsub("%s"," ") text=text:gsub("\\N","\\N~") text=text.."**" text=re.sub(text,"([\\w[:punct:]\\s])([\\w[:punct:]\\s])([\\w[:punct:]\\s])","{\\"..k1.."}\\1{\\"..k2.."}\\2{\\"..k3.."}\\3") text=text:gsub("{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s"," "):gsub("{\\%d? c&H%x+&}~","") end end

if res.clrs=="4" then if res.word then text=text.." * * * " text=re.sub(text,"([\\w[:punct:]]+) ([\\w[:punct:]]+) ([\\w[:punct:]]+) ([\\w[:punct:]]+) ","{\\"..k1.."}\\1 {\\"..k2.."}\\2 {\\"..k3.."}\\3 {\\"..k4.."}\\4 ") else text=text:gsub("%s"," ") text=text:gsub("\\N","\\N\\N") text=text.."***" text=re.sub(text,"([\\w[:punct:]\\s])([\\w[:punct:]\\s])([\\w[:punct:]\\s])([\\w[:punct:]\\s])","{\\"..k1.."}\\1{\\"..k2.."}\\2{\\" ..k3.."}\\3{\\"..k4.."}\\4") text=text:gsub("{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s{\\%d? c&H%x+&}%s"," ") end end

if res.clrs=="5" then if res.word then text=text.." * * * * " text=re.sub(text,"([\\w[:punct:]]+) ([\\w[:punct:]]+) ([\\w[:punct:]]+) ([\\w[:punct:]]+) ([\\w[:punct:]]+) ","{\\"..k1.."}\\1 {\\"..k2.."}\\2 {\\"..k3.."}\\3 {\\"..k4.."}\\4 {\\"..k5.."}\\5 ") else text=text:gsub("%s"," ") text=text:gsub("\\N","\\N\\N~") text=text.."****" text=re.sub(text,"([\\w[:punct:]\\s])([\\w[:punct:]\\s])([\\w[:punct:]\\s])([\\w[:punct:]\\s])([\\w[:punct:]\\s])","{\\"..k1.."}\\ 1{\\"..k2.."}\\2{\\"..k3.."}\\3{\\"..k4.."}\\4{\\"..k5.."}\\5") text=text:gsub("{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s{\\%d?c&H%x+&}%s{\\%d? c&H%x+&}%s{\\%d?c&H%x+&}%s"," "):gsub("{\\%d?c&H%x+&}~","") end end

text=text:gsub("{\\%d?c&H%x+&}%*",""):gsub("[%s%*]+$",""):gsub(" $","") :gsub("{\\%d?c&H%x+&}\\{\\%d?c&H%x+&}N","\\N"):gsub("\\N\\N","\\N") text=tags..text text=text:gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) if not res.join then text=text:gsub("}{","") end if orig:match("{%*?\\") then text=textmod(orig) end text=text..comm line.text=text subs[i]=line end end

-- Tune Colours -- function ctune(subs,sel) if res.tuneall then tuneallc="" for z,i in ipairs(sel) do t=subs[i].text if res.tfmode=="regular" then t=t:gsub("\\t%b()","") end if res.tfmode=="transf" then nt="" for tf in t:gmatch("\\t%b()") do nt=nt..tf end t=nt end for kol in t:gmatch("\\%d?c%b&&") do if not tuneallc:match(kol) then tuneallc=tuneallc..kol end end end tuneallc=tuneallc:gsub("\\c&","\\1c&") tunegui() for l=1,4 do if tuneallc:match(l.."c&") then table.insert(colortunegui,lbls[l]) end end for col in tuneallc:gmatch("(\\[1234]c&H%x%x%x%x%x%x&)") do cType,B,G,R=col:match("\\([1234])c&H(%x%x)(%x%x)(%x%x)&") ctNo=tonumber(cType) C="#"..R..G..B table.insert(colortunegui,{x=cType,y=wai[ctNo],class="color",name=cType..wai[ctNo],value=C}) wai[ctNo]=wai[ctNo]+1 end pressed,rez=ADD(colortunegui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pressed=="Cancel" then ak() end replcol={} for k,v in ipairs(colortunegui) do if v.class=="color" then c1="\\"..v.x.."c"..v.value:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") c2="\\"..v.x.."c"..rez[v.name]:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") table.insert(replcol,{c1=c1,c2=c2}) end end end for z=1,#sel do i=sel[z] progress("Processing... "..z.."/"..#sel) line=subs[i] text=line.text:gsub("\\c&","\\1c&") if res.tuneall then text=alltune(text) elseif text:match("c&H%x+&") then tunegui(z) tekst={} ccheck={} text=tunec(text) end text=text:gsub("\\1c&","\\c&") line.text=text subs[i]=line end pressed=nil end function tunegui(z) wai={1,1,1,1} chk={0,0,0,0} lbls={{label="primary"},{label="2ndary"},{label="border"},{label="shadow"}} for l=1,4 do lbls[l].class="label" lbls[l].x=l end if res.tuneall then colortunegui={} else colortunegui={{class="label",label="#"..z}} end end function alltune(text) segments={} text=text:gsub("\\t%([^\\%)]-%)","") if text:match("\\t%b()") then for seg1,seg2 in text:gmatch("(.-)(\\t%b())") do table.insert(segments,seg1) table.insert(segments,seg2) end table.insert(segments,text:match("^.*\\t%b()(.-)$")) else table.insert(segments,text) end nt="" for q=1,#segments do if segments[q]:match("\\t%b()") and modetf then segments[q]=replicolor(segments[q]) elseif not segments[q]:match("\\t%b()") and modereg then segments[q]=replicolor(segments[q]) end nt=nt..segments[q] end return nt end function replicolor(t) for rc=1,#replcol do t=t:gsub(replcol[rc].c1,replcol[rc].c2) end return t end function tunec(text) segments={} text=text:gsub("\\t%([^\\%)]-%)","") if text:match("\\t%b()") then for seg1,seg2 in text:gmatch("(.-)(\\t%b())") do table.insert(segments,seg1) table.insert(segments,seg2) end table.insert(segments,text:match("^.*\\t%b()(.-)$")) else table.insert(segments,text) end for q=1,#segments do if segments[q]:match("\\t%b()") and modetf then segments[q]=tune(segments[q]) elseif not segments[q]:match("\\t%b()") and modereg then segments[q]=tune(segments[q]) else table.insert(tekst,segments[q]) table.insert(ccheck,0) end end

pressed,rez=ADD(colortunegui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pressed=="Cancel" then ak() end

text="" rezlt={1,1,1,1} for c=1,#tekst do nt=tekst[c] if ccheck[c]==1 then col=nt:match("\\[1234]c&H%x%x%x%x%x%x&") cType,B,G,R=col:match("\\([1234])c&H(%x%x)(%x%x)(%x%x)&") ctNo=tonumber(cType) cor=esc(col) crep="\\"..cType.."c"..rez[cType..rezlt[ctNo]]:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") text=text..nt:gsub(cor,crep) rezlt[ctNo]=rezlt[ctNo]+1 else text=text..nt end end return text end function tune(txt) for t,col in txt:gmatch("(.-)(\\[1234]c&H%x%x%x%x%x%x&)") do cType,B,G,R=col:match("\\([1234])c&H(%x%x)(%x%x)(%x%x)&") ctNo=tonumber(cType) C="#"..R..G..B if chk[ctNo]==0 then table.insert(colortunegui,lbls[ctNo]) chk[ctNo]=1 end table.insert(colortunegui,{x=cType,y=wai[ctNo],class="color",name=cType..wai[ctNo],value=C}) table.insert(tekst,t..col) table.insert(ccheck,1) wai[ctNo]=wai[ctNo]+1 end final=txt:match(".*\\[1234]c&H%x%x%x%x%x%x&(.-)$") if not final then final=txt end table.insert(tekst,final) table.insert(ccheck,0) return txt end

-- Colours across line -- function gcolors(subs,sel) cn=tonumber(res.gclrs) fn=cn-1 -- factors table fakt={0} for f=1,fn do fk=f/fn table.insert(fakt,fk) end -- GUI gc_config={{x=0,y=0,class="dropdown",name="gctype",items={"\\c","\\3c","\\4c","\\2c"},value="\\c"}} for c=1,cn do cte={x=c,y=0,class="color",name="gc"..c} table.insert(gc_config,cte) end button={"This is a rather big button","Click this, and something might happen","What is this, I don't even","The do- not-cancel button","I accept the terms of this scam","Is this really the right button?","What do I do with all these colours?!","I sure hope nothing will break if I do this","Yeah, okay. Fine. I'm gonna click on this.","Is this button safe to click?","Anyone else feels like this is a bit random?","We interrupt your typesetting to bring you a button!","I assure you this script actually works (maybe)","No, but seriously, click me!"} ex=math.random(1,#button) if not res.rept then press,rez=ADD(gc_config,{button[ex],"Cancel"},{close='Cancel'}) end if press=="Cancel" then ak() end kt=rez.gctype -- colours table kolors={} for c=1,cn do gcol=rez["gc"..c]:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") gcol=kt..gcol table.insert(kolors,gcol) end for z,i in ipairs(sel) do progress("Colorizing line "..z.."/"..#sel) line=subs[i] text=line.text text=text:gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end) :gsub("\\1c","\\c") :gsub(kt.."&H%x+&","") :gsub("{}","") tags=text:match(STAG) or "" orig=text:gsub(STAG,"") breaks=text:gsub("%b{}","") text=breaks:gsub("\\N","") clean=text:gsub(" ","") len=re.find(clean,".") nt="" for n=cn,1,-1 do lngth=math.ceil((#len-1)*fakt[n]) kolr=kolors[n] seg=re.sub(text,"[\\w[:punct:]\\=\\+\\^\\$]\\s?","",lngth) if lngth==0 then seg=text end text=text:gsub(esc(seg).."$","") seg="{"..kolr.."}"..seg nt=seg..nt end text=nt text=tags..textmod(orig) text=text:gsub("({\\[^}]-)}{(\\[^}]-})","%1%2") :gsub(ATAG,function(tg) repeat tg,r=tg:gsub(kt.."[%d%.%-]+([^}]-)("..kt.."[%d%.%-]+)","%2%1") until r==0 return tg end) :gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end)

for breakpos in breaks:gmatch("(.-)\\N") do BPL=breakpos:len() if LBP then BPL=BPL+LBP+2 end if BPL>0 then text=insertxt(text,BPL,"\\N") end LBP=BPL or 0 end LBP=nil

line.text=text subs[i]=line end end function insertxt(text,txtpos,thing) pos=0 tcount=0 for tg,tx in text:gmatch("("..ATAG..")([^{]*)") do sl=tx:len() tl=tg:len() if sl+pos

-- Shift colours -- function shift(subs,sel) klrs=tonumber(res.clrs) -- how many colours we're dealing with count=1 -- start line counter if res.shit=="line" then sline=true else sline=false end for z,i in ipairs(sel) do progress("Colorizing line "..z.."/"..#sel) line=subs[i] text=line.text

-- check if line looks colorized ccc=re.find(text,"\\{\\\\[1234]?c&H[A-Fa-f0-9]+&\\}[\\w[:punct:]]") if not ccc then t_error("Line "..z.." does not \nappear to be colorized",1) end

-- determine which colour has been used to colorize - 1c, 2c, 3c, 4c if sline then matches=re.find(text,"\\{\\*?\\\\[1234]?c&H[A-Fa-f0-9]+&\\}[^\\{]*$") cms=matches[1].str ctype,shc=cms:match("{%*?(\\[1234]?c)(&H%x+&)}[^{]*$") first="{"..ctype..shc.."}" else matches=re.find(text,"\\{\\\\[1234]?c&H[A-Fa-f0-9]+&\\}[\\w[:punct:]]") cms=matches[1].str ctype=cms:match("\\[1234]?c")

-- get colours 2, 3, 4, 5, and create sequences for shifting matches=re.match(text,"([\\w[:punct:]]\\s?)(\\{\\"..ctype.."&H[A-Fa-f0-9]+&\\})([\\w[:punct:]]\\s? )(\\{\\"..ctype.."&H[A-Fa-f0-9]+&\\})([\\w[:punct:]]\\s?)(\\{\\"..ctype.."&H[A-Fa-f0-9]+&\\})([\\w[:punct:]]\\s? )(\\{\\"..ctype.."&H[A-Fa-f0-9]+&\\})") if matches==nil then matches=re.match(text,"([\\w[:punct:]]\\s?)(\\{\\"..ctype.."&H[A-Fa-f0-9]+&\\})([\\w[:punct:]]\\s? )(\\{\\"..ctype.."&H[A-Fa-f0-9]+&\\})") c2=matches[3].str c3=matches[5].str else c2=matches[3].str c3=matches[5].str c4=matches[7].str c5=matches[9].str end

if klrs==2 then first=c2 end if klrs==3 then first=c3 second=c2 end if klrs==4 then first=c4 second=c3 third=c2 end if klrs==5 then first=c5 second=c4 third=c3 fourth=c2 end end

-- don't run for 1st lines in sequences if count>1 or not res.cont then

-- separate first colour tag from other tags, save initial tags tags="" if text:match("^{[^}]*"..ctype.."&") then text=text:gsub("^({[^}]*)("..ctype.."&H%x+&)([^}]*})","%1%3{%2}") end if not text:match("^{\\%d?c&H%x+&}") then tags=text:match(STAG) or "" text=text:gsub("^{\\[^}]*}","") end

-- shifting colours happens here switch=1 repeat text=re.sub(text,"(\\{\\*?\\\\[1234]?c&H[A-Fa-f0-9]+&\\})([\\w[:punct:]])","\\2\\1") text=re.sub(text,"(\\{\\*?\\\\[1234]?c&H[A-Fa-f0-9]+&\\})(\\s)","\\2\\1") text=re.sub(text,"(\\{\\*?\\\\[1234]?c&H[A-Fa-f0-9]+&\\})(\\\\N)","\\2\\1") text=re.sub(text,"(\\{\\*?\\\\[1234]?c&H[A-Fa-f0-9]+&\\})$","") text=text:gsub("{}","") text=first..text switch=switch+1 if not sline then if switch==2 then first=second end if switch==3 then first=third end if switch==4 then first=fourth end else matches=re.find(text,"\\{\\*?\\\\[1234]?c&H[A-Fa-f0-9]+&\\}[^\\{]*$") cms=matches[1].str ctype,shc=cms:match("{%*?(\\[1234]?c)(&H%x+&)}[^{]*$") first="{"..ctype..shc.."}" end for cl1,cl2,cl3 in text:gmatch("({\\[1234]?c&H%x+&})(.)({\\[1234]?c&H%x+&})") do if cl1==cl3 then text=text:gsub(cl1..cl2..cl3,cl1..cl2) end end until switch>=count

text=tags..text if res.join==false then text=text:gsub("}{","") end end

-- line counter if res.cont then count=count+1 end if not sline and count>klrs then count=1 end line.text=text subs[i]=line end end

-- Match colours -- function matchcolors(subs,sel) if P=="Match Colours" then _=0 for key,val in ipairs(GUI) do if val.name and val.name:match"match" and res[val.name] then _=_+1 end if val.name and val.name=="invert" and res[val.name] then _=_+1 end end if _>1 then t_error("Multiple checkboxes for matching checked.\nResults may be unpredictable.") end end for z,i in ipairs(sel) do progress("Colorizing line "..z.."/"..#sel) line=subs[i] text=line.text if defaref and line.style=="Default" then sr=defaref elseif lastref and laststyle==line.style then sr=lastref else sr=stylechk(line.style) end lastref=sr laststyle=line.style stylecol=stylecolours() text=text:gsub("\\1c","\\c") if not text:match("^{\\") then text=text:gsub("^","{\\}") end tags=text:match(STAG) notftags=tags:gsub("\\t%b()","")

-- 1-->3 match outline to primary if P=="Match Colours" and res.match13 then if not notftags:match("\\c&") then text=addtag3("\\c"..primary,text) end text=macchi(text,"\\c","\\3c") end

-- 3-->1 match primary to outline if P=="Match Colours" and res.match31 then if not notftags:match("\\3c") then text=addtag3("\\3c"..outline,text) end text=macchi(text,"\\3c","\\c") end

-- 1-->4 match shadow to primary if P=="Match Colours" and res.match14 then if not notftags:match("\\c&") then text=addtag3("\\c"..primary,text) end text=macchi(text,"\\c","\\4c") end

-- 3-->4 match shadow to outline if P=="Match Colours" and res.match34 then if not notftags:match("\\3c") then text=addtag3("\\3c"..outline,text) end text=macchi(text,"\\3c","\\4c") end

-- 1<-->3 switch primary and border if P=="Match Colours" and res.match131 then if not notftags:match("\\c&") then text=addtag3("\\c"..primary,text) end if not notftags:match("\\3c") then text=addtag3("\\3c"..outline,text) end text=text:gsub("\\c&","\\tempc&"):gsub("\\3c","\\c"):gsub("\\tempc","\\3c") end

-- Invert All Colours if P=="Match Colours" and res.invert then for n=1,4 do ctg="\\"..n.."c" ctg=ctg:gsub("1","") if not notftags:match(ctg) and n~=2 then text=addtag3(ctg..stylecol[n],text) end end for tg,color in text:gmatch("(\\[1234]?c&H)(%x%x%x%x%x%x)&") do icolor="" for kol in color:gmatch("(%x%x)") do dkol=tonumber(kol,16) idkol=255-dkol ikol=tohex(idkol) icolor=icolor..ikol end text=text:gsub(tg..color,tg..icolor) end end

-- RGB / HSL if P=="RGB" or P=="HSL" then lvlr=res.R lvlg=res.G lvlb=res.B hue=res.huehue sat=res.satur brite=res.bright corols={} if res.k1 then table.insert(corols,1) end if res.k2 then table.insert(corols,2) end if res.k3 then table.insert(corols,3) end if res.k4 then table.insert(corols,4) end for i=1,#corols do kl="\\"..corols[i].."c" kl=kl:gsub("1","") if res.mktag and not notftags:match(kl) then text=addtag3(kl..stylecol[corols[i]],text) end if P=="RGB" then text=rgbhslmod(text,kl,rgbm) end if P=="HSL" then text=rgbhslmod(text,kl,hslm) end end end

text=text:gsub("\\([\\}])","%1") :gsub("\\t%([^\\%)]*%)","") :gsub("{}","") line.text=text subs[i]=line end end function macchi(text,c1,c2) text=text:gsub(ATAG,function(ctags) ctags=ctags:gsub(c2..ACLR,""):gsub(c1.."("..ACLR..")",c1.."%1"..c2.."%1") return ctags end) return text end function rgbhslmod(text,kl,ef) segments={} if text:match("\\t%b()") then for seg1,seg2 in text:gmatch("(.-)(\\t%b())") do table.insert(segments,seg1) table.insert(segments,seg2) end table.insert(segments,text:match("^.*\\t%b()(.-)$")) else table.insert(segments,text) end for q=1,#segments do if segments[q]:match("\\t%b()") and modetf then segments[q]=ef(segments[q],kl) end if not segments[q]:match("\\t%b()") and modereg then segments[q]=ef(segments[q],kl) end end nt="" for q=1,#segments do nt=nt..segments[q] end return nt end function rgbm(text,kl) for kol1,kol2,kol3 in text:gmatch(kl.."&H(%x%x)(%x%x)(%x%x)&") do kol1n=brightness(kol1,lvlb) kol2n=brightness(kol2,lvlg) kol3n=brightness(kol3,lvlr) text=text:gsub(kl.."&H"..kol1..kol2..kol3,kl.."&H"..kol1n..kol2n..kol3n) end return text end function hslm(text,kl) for kol1,kol2,kol3 in text:gmatch(kl.."&H(%x%x)(%x%x)(%x%x)&") do H1,S1,L1=RGB_to_HSL(kol3,kol2,kol1) H=H1+hue/255 S=S1+sat/255 L=L1+brite/255 if randomize then H2=H1-hue/255 S2=S1-sat/255 L2=L1-brite/255 H=math.random(H*1000,H2*1000)/1000 S=math.random(S*1000,S2*1000)/1000 L=math.random(L*1000,L2*1000)/1000 end H,S,L=HSLround(H,S,L) kol3n,kol2n,kol1n=HSL_to_RGB(H,S,L) kol3n=tohex(round(kol3n)) kol2n=tohex(round(kol2n)) kol1n=tohex(round(kol1n)) text=text:gsub(kl.."&H"..kol1..kol2..kol3,kl.."&H"..kol1n..kol2n..kol3n) end return text end

-- GRADIENT -- function gradient(subs,sel) styleget(subs) if res.grtype=="RGB" then GRGB=true else GRGB=false end if res.ast then ast="*" else ast="" end for z,i in ipairs(sel) do progress("Colorizing line "..z.."/"..#sel) line=subs[i] text=line.text acc=line.effect:match("accel ?(%d+%.?%d*)") text=text:gsub("\\c&","\\1c&") :gsub(" *\\N *","{\\N}") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end) after=text:gsub(STAG,"") nc=text:gsub(COMM,"") if text:match(ATAG.."$") then text=text.."wtfwhywouldyoudothis" end

-- colours from style sr=stylechk(line.style) stylecol=stylecolours() -- which types will be used applycol={} if res.k1 and after:match("\\1c") then table.insert(applycol,1) end if res.k2 and after:match("\\2c") then table.insert(applycol,2) end if res.k3 and after:match("\\3c") then table.insert(applycol,3) end if res.k4 and after:match("\\4c") then table.insert(applycol,4) end for g=1,#applycol do ac=applycol[g] sc=stylecol[ac] tags=text:match(STAG) or "" -- linebreak adjustment if res.gradn then startc=tags:match("\\"..ac.."c&H%x+&") or "\\"..ac.."c"..sc endc=nc:match("(\\"..ac.."c&H%x+&)[^}]-}%S+$") or "" text=text:gsub("([%S])%s*{\\N","{"..endc.."}%1{\\N}{"..startc) end -- back up original orig=text -- leave only releavant colour tags, nuke all other ones, add colour from style if missing at the start ctext=text:gsub("\\N","") :gsub("\\[^1234][^c][^\\}]+","") :gsub("\\[^"..ac.."]c[^\\}]+","") :gsub("{%**}","") if not ctext:match("^{\\") then ctext="{\\kolor}"..ctext end if not ctext:match("^{[^}]-\\"..ac.."c") then ctext=ctext:gsub("^({\\[^}]-)}","%1\\"..ac.."c"..sc.."}") end -- make tables of colour tags and text after them linecol={} posi={} coltext={} pos=0 for k,t in ctext:gmatch("{[^}]-\\"..ac.."c&H(%x+)&[^}]-}([^{]+)") do table.insert(posi,pos) table.insert(linecol,k) table.insert(coltext,t) ps=re.find(t,".") pos=#ps end

-- text for each colour gradtext=""

-- sequence for each colour tag / text for c=1,#linecol-1 do -- get RBG and HSL if needed B1,G1,R1=linecol[c]:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=linecol[c+1]:match("(%x%x)(%x%x)(%x%x)") if not GRGB then H1,S1,L1=RGB_to_HSL(R1,G1,B1) H2,S2,L2=RGB_to_HSL(R2,G2,B2) if res.hueshort then if H2>H1 and H2-H1>0.5 then H1=H1+1 end if H2

0.5 then H2=H2+1 end end if res.double then if H2>H1 then H2=H2+1 else H1=H1+1 end if H1>2 or H2>2 then H2=H2-1 H1=H1-1 end end end -- letters of this sequence textseq={} ltrmatches=re.find(coltext[c],".") for l=1,#ltrmatches do table.insert(textseq,ltrmatches[l].str) end -- new text starting with original colour tag and first letter ntxt="{\\"..ac.."c&H"..linecol[c].."&}"..textseq[1] -- calculate colours for the other letters in sequence for l=2,posi[c+1] do if textseq[l]~=" " then if GRGB then -- RGB NC=acgrad(linecol[c],linecol[c+1],posi[c+1]+1,l,acc) else -- HSL Hdiff=(H2-H1)/(posi[c+1]+1) H=H1+Hdiff*l Sdiff=(S2-S1)/(posi[c+1]+1) S=S1+Sdiff*l Ldiff=(L2-L1)/(posi[c+1]+1) L=L1+Ldiff*l R,G,B=HSL_to_RGB(H,S,L) R=tohex(round(R)) G=tohex(round(G)) B=tohex(round(B)) NC="&H"..B..G..R.."&" end ncol="{"..ast.."\\"..ac.."c"..NC.."}" -- colour + letter ntxt=ntxt..ncol..textseq[l] else -- spaces (no tags) ntxt=ntxt..textseq[l] end end gradtext=gradtext..ntxt end -- add final tag + text gradtext=gradtext.."{\\"..ac.."c&H"..linecol[#linecol].."&}"..coltext[#coltext] text=tags..gradtext -- merge with original text=textmod(orig) end text=text:gsub("({%*?\\[^}]-})",function(tg) return colkill(tg) end) text=text:gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) :gsub("wtfwhywouldyoudothis","") :gsub("{([^}]-)\\N([^}]-)}","\\N{%1%2}") :gsub("{%**}","") :gsub("([^{])%*\\","%1\\") line.text=text subs[i]=line end end

-- Reverse Gradient -- function rvrsgrad(subs,sel) for z,i in ipairs(sel) do progress("Colorizing line "..z.."/"..#sel) line=subs[i] text=line.text text=text:gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end) :gsub("\\c&","\\1c&") after=text:gsub(STAG,"") applycol={} if res.k1 and after:match("\\1c") then table.insert(applycol,1) end if res.k2 and after:match("\\2c") then table.insert(applycol,2) end if res.k3 and after:match("\\3c") then table.insert(applycol,3) end if res.k4 and after:match("\\4c") then table.insert(applycol,4) end

for g=1,#applycol do ac=applycol[g] tagtab={} coltab={} for tt,cc in text:gmatch("(.-)(\\"..ac.."c&H%x+&)") do table.insert(tagtab,tt..cc) table.insert(coltab,cc) end END=text:match("^.*\\"..ac.."c&H%x+&(.-)$") for t=1,#tagtab do o=#tagtab-t+1 tagtab[t]=tagtab[t]:gsub("\\"..ac.."c&H%x+&",coltab[o]) end nt=END for a=#tagtab,1,-1 do nt=tagtab[a]..nt end text=nt end

text=text:gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) :gsub("\\1c&","\\c&") line.text=text subs[i]=line end end function stylecolours() stylecol={} notf=text:gsub("\\t%b()","") table.insert(stylecol,(sr.color1:gsub("H%x%x","H"))) primary=notf:match("^{[^}]-\\c(&H%x+&)") or stylecol[1] table.insert(stylecol,(sr.color2:gsub("H%x%x","H"))) secondary=notf:match("^{[^}]-\\3c(&H%x+&)") or stylecol[2] table.insert(stylecol,(sr.color3:gsub("H%x%x","H"))) outline=notf:match("^{[^}]-\\3c(&H%x+&)") or stylecol[3] table.insert(stylecol,(sr.color4:gsub("H%x%x","H"))) shadow=notf:match("^{[^}]-\\c(&H%x+&)")or stylecol[4] return stylecol end function acgrad(C1,C2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc B1,G1,R1=C1:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=C2:match("(%x%x)(%x%x)(%x%x)") A1=C1:match("(%x%x)") R1=R1 or A1 A2=C2:match("(%x%x)") R2=R2 or A2 nR1=(tonumber(R1,16)) nR2=(tonumber(R2,16)) R=acc_fac*(nR2-nR1)+nR1 R=tohex(round(R)) CC="&H"..R.."&" if B1 then nG1=(tonumber(G1,16)) nG2=(tonumber(G2,16)) nB1=(tonumber(B1,16)) nB2=(tonumber(B2,16)) G=acc_fac*(nG2-nG1)+nG1 B=acc_fac*(nB2-nB1)+nB1 G=tohex(round(G)) B=tohex(round(B)) CC="&H"..B..G..R.."&" end return CC end function textmod(orig) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 vis=text:gsub("{[^}]-}","") ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end stags=text:match(STAG) or "" text=text:gsub(STAG,"") :gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n,t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext=stags..newline text=newtext:gsub("{}","") return text end function colkill(tagz) tagz=tagz:gsub("\\1c&","\\c&") tags2={"c","2c","3c","4c"} for i=1,#tags2 do tag=tags2[i] tagz=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") end return tagz end function RGB_to_HSL(Red,Green,Blue) R=(tonumber(Red,16)/255) G=(tonumber(Green,16)/255) B=(tonumber(Blue,16)/255)

Min=math.min(R,G,B) Max=math.max(R,G,B) del_Max=Max-Min

L=(Max+Min)/2

if del_Max==0 then H=0 S=0 else if L<0.5 then S=del_Max/(Max+Min) else S=del_Max/(2-Max-Min) end

del_R=(((Max-R)/6)+(del_Max/2))/del_Max del_G=(((Max-G)/6)+(del_Max/2))/del_Max del_B=(((Max-B)/6)+(del_Max/2))/del_Max

if R==Max then H=del_B-del_G elseif G==Max then H=(1/3)+del_R-del_B elseif B==Max then H=(2/3)+del_G-del_R end

if H<0 then H=H+1 end if H>1 then H=H-1 end end return H,S,L end function HSL_to_RGB(H,S,L) if S==0 then R=L*255 G=L*255 B=L*255 else if L<0.5 then var_2=L*(1+S) else var_2=(L+S)-(S*L) end var_1=2*L-var_2 R=255*Hue_to_RGB(var_1,var_2,H+(1/3)) G=255*Hue_to_RGB(var_1,var_2,H) B=255*Hue_to_RGB(var_1,var_2,H-(1/3)) end return R,G,B end function Hue_to_RGB(v1,v2,vH) if vH<0 then vH=vH+1 end if vH>1 then vH=vH-1 end if (6*vH)<1 then return(v1+(v2-v1)*6*vH) end if (2*vH)<1 then return(v2) end if (3*vH)<2 then return(v1+(v2-v1)*((2/3)-vH)*6) end return(v1) end function HSLround(H,S,L) if H>1 then H=H-1 end if H<0 then H=H+1 end if S>1 then S=1 end if S<0 then S=0 end if L>1 then L=1 end if L<0 then L=0 end return H,S,L end function brightness(klr,lvl) klr=tonumber(klr,16) if randomize then rAn=math.random(klr-lvl,klr+lvl) klr=round(rAn) else klr=klr+lvl end if klr<0 then klr=0 end if klr<10 then klr="0"..klr else klr=tohex(klr) end return klr end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function styleget(subs) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i]) end if subs[i].class=="dialogue" then break end end end function stylechk(sn) for i=1,#styles do if sn==styles[i].name then sr=styles[i] if styles[i].name=="Default" then defaref=styles[i] end break end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function round(num) num=math.floor(num+0.5) return num end function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") if not tgt then t_error("Adding tag '"..tg.."' failed.") end if no_tf:match("^({[^}]-)"..tgt.."[%d%-&]") then txt=txt:gsub("^({[^}]-)"..tgt.."[%d%-&][^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function repetition() if lastres and res.rept then res=lastres end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end function saveconfig() colconf="Colorize Configutation\n\n" for key,val in ipairs(GUI) do if val.class=="checkbox" and val.name~="save" then colconf=colconf..val.name..":"..tf(res[val.name]).."\n" end if val.class=="dropdown" then colconf=colconf..val.name..":"..res[val.name].."\n" end end colorconfig=ADP("?user").."\\colorize.conf" file=io.open(colorconfig,"w") file:write(colconf) file:close() ADD({{class="label",label="Config Saved to:\n"..colorconfig}},{"OK"},{close='OK'}) end function loadconfig() colorconfig=ADP("?user").."\\colorize.conf" file=io.open(colorconfig) if file~=nil then konf=file:read("*all") io.close(file) for key,val in ipairs(GUI) do if val.class=="checkbox" or val.class=="dropdown" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end end function colorize(subs,sel) STAG="^{\\[^}]-}" ATAG="{%*?\\[^}]-}" COMM="{[^\\}]-}" ACLR="&H%x+&" ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel GUI={ {x=0,y=0,class="label",label="Colours"}, {x=1,y=0,width=2,class="dropdown",name="clrs",items={"2","3","4","5"},value="2",hint="number of colours for\n'colorize letter by letter'"},

{x=0,y=1,class="label",label="Apply to: "},

{x=1,y=1,width=2,class="dropdown",name="kol",items={"primary","border","shadow","secondary"},value="primary" },

{x=0,y=2,class="label",label="Shift base:"}, {x=1,y=2,width=2,class="dropdown",name="shit",items={"# of colours","line"},value="# of colours",hint="shift by the number of colours the line had been colorized with,\nor shift the whole line (last colour becomes first)"},

{x=4,y=0,class="label",label=" 1 "}, {x=4,y=1,class="label",label=" 2 "}, {x=4,y=2,class="label",label=" 3 "}, {x=4,y=3,class="label",label=" 4 "}, {x=4,y=4,class="label",label=" 5 "},

{x=5,y=0,class="color",name="c1"}, {x=5,y=1,class="color",name="c2"}, {x=5,y=2,class="color",name="c3"}, {x=5,y=3,class="color",name="c4"}, {x=5,y=4,class="color",name="c5"},

{x=5,y=6,class="dropdown",name="tfmode",items={"all","regular","transf"},value="all",hint="all tags / regular tags / tags in transforms\napplies to 'Tune colours' and RGB/HSL"},

{x=0,y=3,width=3,class="checkbox",name="word",label="Colorize by word"}, {x=0,y=4,width=3,class="checkbox",name="join",label="Don't join with other tags"}, {x=0,y=5,width=4,class="checkbox",name="cont",label="Continuous shift line by line"}, {x=0,y=6,width=2,class="checkbox",name="tune",label="Tune colours"}, {x=2,y=6,width=3,class="checkbox",name="tuneall",label="All selected",hint="load from / apply to all selected lines\nrather than one by one"}, {x=0,y=7,width=5,class="checkbox",name="gcl",label="Set colours across whole line:"}, {x=5,y=7,class="dropdown",name="gclrs",items={"2","3","4","5","6","7","8","9","10"},value="3"},

{x=6,y=0,class="label",label=" "},

{x=7,y=2,class="label",label="Red: "}, {x=8,y=2,width=3,class="intedit",name="R",value=0,min=-255,max=255}, {x=7,y=3,class="label",label="Green: "}, {x=8,y=3,width=3,class="intedit",name="G",value=0,min=-255,max=255}, {x=7,y=4,class="label",label="Blue: "}, {x=8,y=4,width=3,class="intedit",name="B",value=0,min=-255,max=255},

{x=7,y=5,class="label",label="Hue:"}, {x=8,y=5,width=3,class="intedit",name="huehue",value=0,min=-255,max=255}, {x=7,y=6,class="label",label="Saturation:"}, {x=8,y=6,width=3,class="intedit",name="satur",value=0,min=-255,max=255}, {x=7,y=7,class="label",label="Lightness:"}, {x=8,y=7,width=3,class="intedit",name="bright",value=0,min=-255,max=255},

{x=7,y=8,class="checkbox",name="k1",label="\\c ",value=true}, {x=8,y=8,class="checkbox",name="k3",label="\\3c "}, {x=9,y=8,class="checkbox",name="k4",label="\\4c "}, {x=10,y=8,class="checkbox",name="k2",label="\\2c"}, {x=7,y=9,width=2,class="checkbox",name="mktag",label="Apply to missing",hint="Apply even to colours without tags in line"}, {x=9,y=9,width=2,class="checkbox",name="randoom",label="Randomize",hint="randomize RGB/HSL within the\nspecified range in each direction"},

{x=7,y=0,class="label",label="Match col.:"}, {x=8,y=0,class="checkbox",name="match13",label="c->3c ",hint="copy primary to outline"}, {x=9,y=0,class="checkbox",name="match31",label="3c->c",hint="copy outline to primary"}, {x=7,y=1,class="checkbox",name="match14",label="c->4c",hint="copy primary to shadow"}, {x=8,y=1,class="checkbox",name="match34",label="3c->4c",hint="copy outline to shadow"}, {x=9,y=1,class="checkbox",name="match131",label="c<->3c",hint="switch primary and outline"}, {x=10,y=1,class="checkbox",name="invert",label="Invert",hint="invert colours"},

{x=10,y=0,class="label",label="[ver. "..script_version.."]"},

{x=0,y=8,width=2,class="checkbox",name="grad",label="Gradient "}, {x=2,y=8,width=3,class="checkbox",name="hueshort",label="Shortest hue",value=true}, {x=5,y=8,class="dropdown",name="grtype",items={"RGB","HSL"},value="HSL"}, {x=0,y=9,width=3,class="checkbox",name="double",label="Double HSL gradient"}, {x=3,y=9,width=3,class="checkbox",name="ast",label="Use asterisks"},

{x=0,y=10,width=3,class="checkbox",name="gradn",label="Restart after each \\N",hint="Restart gradient after each linebreak"}, {x=0,y=11,width=3,class="checkbox",name="reverse",label="Reverse gradient", hint="Reverse the direction of \nnon-transform colours across the line"},

{x=7,y=10,width=2,class="checkbox",name="rem",label="Remember last",hint="Remember last settings"}, {x=9,y=10,width=2,class="checkbox",name="rept",label="Repeat last",hint="Repeat with last settings"}, {x=7,y=11,class="checkbox",name="help",label="Help", hint="Loads Help. Topic menu is on the left.\nUse any button that's not 'Cancel'."}, {x=9,y=11,width=2,class="checkbox",name="save",label="Save config",hint="Saves current configuration\n(for most things)"},

{x=4,y=11,width=2,class="dropdown",name="hmenu",value="colorize",hint="Help menu", items={"colorize","shift","tunecolours","setcolours","gradient","reverse","match","RGBHSL","general"}} } loadconfig() if colourblind and res.rem then for key,val in ipairs(GUI) do if val.class=="checkbox" or val.class=="dropdown" or val.class=="color" then val.value=res[val.name] end if val.name=="save" then val.value=false end end end HELP=false NOHELP=true repeat if HELP then if NOHELP then table.insert(GUI,{x=0,y=12,width=11,height=7,class="textbox",name="helpbox"}) end for key,val in ipairs(GUI) do if val.class=="checkbox" or val.class=="dropdown" or val.class=="color" then val.value=res[val.name] end if val.name=="save" then val.value=false end if val.name=="helpbox" then val.value=info[res.hmenu] end end NOHELP=false end P,res=ADD(GUI,{"Colorize","Shift","Match Colours","RGB","HSL","Cancel"},{ok='Colorize',close='Cancel'}) HELP=res.help until P=="Cancel" or not res.help if P=="Cancel" then ak() end if res.save then saveconfig() ak() end

randomize=res.randoom if res.tfmode=="all" or res.tfmode=="regular" then modereg=true else modereg=false end if res.tfmode=="all" or res.tfmode=="transf" then modetf=true else modetf=false end if P=="Colorize" then repetition() if res.reverse then rvrsgrad(subs,sel) elseif res.grad then gradient(subs,sel) elseif res.gcl then gcolors(subs,sel) elseif res.tune then ctune(subs,sel) else colors(subs,sel) end end if P=="Shift" then repetition() shift(subs,sel) end if P=="Match Colours" or P=="RGB" or P=="HSL" then repetition() styleget(subs) matchcolors(subs,sel) end

lastres=res colourblind=true aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(colorize) else aegisub.register_macro(script_name,script_description,colorize) end --[[ Script for encoding / hardsubbing

Options:

- encode whole video / a clip - hardsub 1 or 2 subtitle files or only encode - use vsfilter or vsfiltermod for each subtitle track - encode to mp4 or mkv - mux with audio

Requirements:

- x264.exe - vsfilter.dll / vsfiltermod.dll for hardsubbing - avisynth (not required when encoding for mocha)

'Encode clip for mocha' does automatically the following (meaning you don't have to do those manually):

- disables 10bit - enables trimming - disables subtitles - sets target to .mp4 - disables avisynth use --]] script_name="Encode - Hardsub" script_description="Encode a clip with or without hardsubs" script_author="unanimated" script_version="1.2" script_namespace="ua.EncodeHardsub" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="1.2.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end function encode(subs,sel) ADD=aegisub.dialog.display ADP=aegisub.decode_path ADO=aegisub.dialog.open ak=aegisub.cancel enconfig=ADP("?user").."\\encode_hardsub.conf" defsett="--crf 18 --ref 10 --bframes 10 --merange 32 --me umh --subme 10 --trellis 2 --direct auto --b-adapt 2 -- partitions all" defmsett="--profile baseline --level 1.0 --crf 16 --fps 24000/1001" scriptpath=ADP("?script").."\\" scriptname=aegisub.file_name() vpath=ADP("?video").."\\" ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame sframe=999999 eframe=0 videoname=nil file=io.open(enconfig) if file~=nil then konf=file:read("*all") io.close(file) xpath=konf:match("xpath:(.-)\n") xpath10=konf:match("xpath10:(.-)\n") sett=konf:match("settings:(.-)\n") vsfpath=konf:match("vsfpath:(.-)\n") vsfmpath=konf:match("vsfmpath:(.-)\n") mmgpath=konf:match("mmgpath:(.-)\n") or "" vtype=konf:match("vtype:(.-)\n") vsf1=konf:match("filter1:(.-)\n") vsf2=konf:match("filter2:(.-)\n") targ=konf:match("targ:(.-)\n") target=konf:match("target:(.-)\n") msett=konf:match("mocha:(.-)\n") settlist=konf:match("(settings1:.*\n)$") or "" else xpath="" xpath10="" vsfpath="" vsfmpath="" mmgpath="" vtype=".mkv" vsf1="vsfilter" vsf2="vsfilter" sett=defsett msett=defmsett settlist="" targ="Same as source" target="" end for i=1,#subs do if subs[i].class=="info" then if subs[i].key=="Video File" then videoname=subs[i].value break end end if subs[i].class~="info" then break end end if videoname==nil then videoname=aegisub.project_properties().video_file:gsub("^.*\\","") end if videoname==nil or videoname=="" or aegisub.frame_from_ms(10)==nil then t_error("No video detected.",1) end vid2=videoname:gsub("%.[^%.]+","") :gsub("_?premux","") :gsub("_?workraw","") vid2=vid2.."_hardsub" for z,i in ipairs(sel) do line=subs[i] start=line.start_time endt=line.end_time sfr=ms2fr(start) efr=ms2fr(endt) if sfreframe then eframe=efr end end

GUI={ {x=0,y=0,class="label",label="x264.exe 8-bit:"}, {x=1,y=0,width=6,class="edit",name="xpath",value=xpath or ""}, {x=7,y=0,class="label",label=" x264 10-bit:"}, {x=8,y=0,width=2,class="edit",name="xpath10",value=xpath10 or ""},

{x=0,y=1,class="label",label="vsfilter.dll:"}, {x=1,y=1,width=6,class="edit",name="vsf",value=vsfpath or ""}, {x=7,y=1,class="label",label=" vsfiltermod.dll:"}, {x=8,y=1,width=2,class="edit",name="vsfm",value=vsfmpath or "",hint="only needed if you're using it"},

{x=0,y=2,class="label",label="Source video:"}, {x=1,y=2,width=6,class="edit",name="vid",value=videoname},

{x=7,y=2,class="label",label=" mkvmerge.exe:"}, {x=8,y=2,width=2,class="edit",name="mmg",value=mmgpath or "",hint="only needed if you're muxing audio"},

{x=0,y=3,class="label",label="Target folder:"}, {x=1,y=3,width=2,class="dropdown",name="targ",value=targ,items={"Same as source","Custom:"}}, {x=3,y=3,width=7,class="edit",name="target",value=target},

{x=0,y=4,class="label",label="Encode name:"}, {x=1,y=4,width=2,class="dropdown",name="vtype",value=vtype,items={".mkv",".mp4"}}, {x=3,y=4,width=7,class="edit",name="vid2",value=vid2},

{x=0,y=5,class="label",label="Primary subs:"}, {x=1,y=5,width=2,class="dropdown",name="filter1",value=vsf1,items={"none","vsfilter","vsfiltermod"}}, {x=3,y=5,width=7,class="edit",name="first",value=scriptpath..scriptname},

{x=0,y=6,class="checkbox",name="sec",label="Secondary:"}, {x=1,y=6,width=2,class="dropdown",name="filter2",value=vsf2,items={"vsfilter","vsfiltermod"}}, {x=3,y=6,width=7,class="edit",name="second",value=secondary or ""},

{x=0,y=7,class="label",label="Encoder settings:"}, {x=1,y=7,width=9,class="edit",name="encset",value=sett},

{x=0,y=8,class="label",label="Settings 4 mocha:"}, {x=1,y=8,width=9,class="edit",name="encmocha",value=msett or defmsett},

{x=0,y=9,class="checkbox",name="trim",label="Trim from:",hint="Encodes only current selection"}, {x=1,y=9,width=3,class="intedit",name="sf",value=sframe}, {x=4,y=9,class="label",label="to: "}, {x=5,y=9,width=2,class="intedit",name="ef",value=eframe}, {x=7,y=9,width=2,class="label",label=" If checked, frames are added to Encode name "}, {x=9,y=9,width=1,class="checkbox",name="audio",label="Mux with audio",hint="whether you're trimming or not"},

{x=0,y=10,width=2,class="checkbox",name="mocha",label="Encode clip for mocha "}, {x=2,y=10,width=1,class="checkbox",name="tenbit",label="10 bit"}, {x=5,y=10,width=1,class="checkbox",name="delbat",label="Delete batch file",value=true}, {x=6,y=10,width=2,class="checkbox",name="delavs",label="Delete avisynth script",value=true}, {x=8,y=10,width=1,class="checkbox",name="delAV",label="Delete A/V after muxing",value=true,hint="Delete audio/video files only needed for muxing"}, {x=9,y=10,width=1,class="checkbox",name="pause",label="Keep cmd window open "}, } repeat if P=="Default enc. settings" then gui("encset",defsett) end if P=="x264" then x264_path=ADO("x264","",scriptpath,"*.exe",false,true) gui("xpath",x264_path) end if P=="x264 10bit" then x26410_path=ADO("x264 10-bit","",scriptpath,"*.exe",false,true) gui("xpath10",x26410_path) end if P=="vsfilter" then vsf_path=ADO("vsfilter","",scriptpath,"*.dll",false,true) gui("vsf",vsf_path) end if P=="vsfiltermod" then vsfm_path=ADO("vsfiltermod","",scriptpath,"*.dll",false,true) gui("vsfm",vsfm_path) end if P=="Target" then tgt_path=ADO("Target folder for encodes (Select any file in it)",".",scriptpath,"",false,false) if tgt_path then tgt_path=tgt_path:gsub("(.*\\).-$","%1") end gui("target",tgt_path) end if P=="Secondary" then sec_path=ADO("Secondary subs","",scriptpath,"*.ass",false,true) gui("second",sec_path) end if P=="Enc. set." then enclist={defsett} for set in settlist:gmatch("settings%d:(.-)\n") do table.insert(enclist,set) end encodings={{class="dropdown",name="enko",items=enclist,value=defsett}} press,rez=ADD(encodings,{"OK","Cancel"},{ok='OK',close='Cancel'}) for k,v in ipairs(GUI) do if v.name=="encset" then v.value=rez.enko else v.value=res[v.name] end end end if P=="Save" then konf="xpath:"..res.xpath.."\nxpath10:"..res.xpath10.."\nvsfpath:"..res.vsf.."\nvsfmpath:"..res.vsfm.."\nmmgpath:"..res.m mg.."\nvtype:"..res.vtype.."\nfilter1:"..res.filter1.."\nfilter2:"..res.filter2.."\ntarg:"..res.targ.."\ntarget:"..res.target.."\nmoc ha:"..res.encmocha.."\nsettings:"..res.encset.."\n" if res.encset~=sett then settlist=settlist:gsub("settings9:.-\n","") set1=esc(sett) if not settlist:match(set1) then for i=8,1,-1 do settlist=settlist:gsub("(settings)"..i,"%1"..i+1) end settlist="settings1:"..sett.."\n"..settlist end end konf=konf..settlist file=io.open(enconfig,"w") file:write(konf) file:close() for k,v in ipairs(GUI) do v.value=res[v.name] end ADD({{class="label",label="Settings saved to:\n"..enconfig}},{"OK"},{close='OK'}) end P,res=ADD(GUI, {"Encode","x264","x264 10bit","vsfilter","vsfiltermod","Target","Secondary","Enc. set.","Save","Cancel"},{ok='Encode',close='Cancel'}) until P=="Encode" or P=="Cancel" if P=="Cancel" then ak() end ------

videoname=res.vid encname=res.vid2 mkvmerge=res.mmg target=vpath vfull=vpath..videoname vsm=0 if res.targ=="Custom:" then target=res.target end

-- mocha if res.mocha then res.vtype=".mp4" res.tenbit=false res.trim=true res.encset=res.encmocha.." --seek "..res.sf.." --frames "..res.ef-res.sf res.delavs=false encname=encname:gsub("_hardsub","") source=quo(vfull) end

if res.filter1=="none" then res.sec=false encname=encname:gsub("_hardsub","_encode") end if res.trim then encname=encname.."_"..res.sf.."-"..res.ef encname=encname:gsub("_encode","") end if res.tenbit then xpath=res.xpath10 else xpath=res.xpath end

file=io.open(xpath) if file==nil then t_error(xpath.."\nERROR: File does not exist (x264).",true) else file:close() end file=io.open(vfull) if file==nil then t_error(vfull.."\nERROR: File does not exist (video source).",true) else file:close() end

-- avisynth if res.mocha==false then if res.filter1~="none" and res.first:match("%?script\\") then t_error("ERROR: It appears your subtitles are not saved.",true) end if res.filter1=="vsfilter" then plug1="loadplugin(\""..res.vsf.."\")\n" text1="textsub("..quo(res.first)..")\n" vsm=1 elseif res.filter1=="vsfiltermod" then plug1="loadplugin(\""..res.vsfm.."\")\n" text1="textsubmod("..quo(res.first)..")\n" vsm=2 else plug1="" text1="" end

if res.filter2=="vsfilter" then filth2=res.vsf ts2="textsub" else filth2=res.vsfm ts2="textsubmod" end if res.sec and res.filter1~=res.filter2 then plug2="loadplugin(\""..filth2.."\")\n" vsm=3 else plug2="" end if res.sec then text2=ts2.."("..quo(res.second)..")\n" else text2="" end if res.trim then trim="Trim("..res.sf..", "..res.ef-1 ..")" else trim="" end

avs=plug1..plug2.."ffvideosource("..quo(vfull)..")\n"..text1..text2..trim

-- vsfilter checks if vsm==1 or vsm==3 then file=io.open(res.vsf) if file==nil then t_error(res.vsf.."\nERROR: File does not exist (vsfilter).",true) else file:close() end end if vsm>1 then file=io.open(res.vsfm) if file==nil then t_error(res.vsfm.."\nERROR: File does not exist (vsfiltermod).",true) else file:close() end end

if scriptpath=="?script\\" then scriptpath=vpath end local avsfile=io.open(scriptpath.."hardsub.avs","w") avsfile:write(avs) avsfile:close()

source=quo(scriptpath.."hardsub.avs") end

-- mkvmerge audio if res.audio then if res.trim then vstart=fr2ms(res.sf) vend=fr2ms(res.ef) timec1=time2string(vstart) timec2=time2string(vend) audiofile=target..encname..".mka" audiosplit=quo(mkvmerge).." -o "..quo(audiofile).." -D -S -M --split parts:"..timec1.."-"..timec2.." "..quo(vfull) merge=audiosplit.."\n"..quo(mkvmerge).." -o "..quo(target..encname.."_muxed.mkv").." "..quo(target..encname..res.vtype).." "..quo(audiofile) else merge=quo(mkvmerge).." -o "..quo(target..encname.."_muxed.mkv").." "..quo(target..encname..res.vtype).." -D - S -M "..quo(vfull) end end

-- batch script encode=quo(xpath).." "..res.encset.." -o "..quo(target..encname..res.vtype).." "..source if res.audio then encode=encode.."\n"..merge end batch=scriptpath.."encode.bat" if res.pause then encode=encode.."\npause" end encode=encode.."\ndel "..quo(target..videoname..".ffindex") if res.audio and res.delAV then encode=encode.."\ndel "..quo(target..encname..res.vtype) if audiofile then encode=encode.."\ndel "..quo(audiofile) audiofile=nil end end if res.delavs then encode=encode.."\ndel "..quo(scriptpath.."hardsub.avs") end if res.delbat then encode=encode.."\ndel "..quo(batch) end

local xfile=io.open(batch,"w") xfile:write(encode) xfile:close()

-- encode if res.tenbit then ten="Yes" else ten="No" end if res.trim then tr=res.sf..","..res.ef else tr="None" end info="Encode name: "..encname..res.vtype.."\n10-bit: "..ten.."\nTrim: "..tr.."\n\nBatch file: "..batch.."\n\nYou can encode now or run this batch file later.\nIf encoding from Aegisub doesn't work,\njust run the batch file.\n\nEncode now?" P=ADD({{class="label",label=info}},{"Yes","No"},{ok='Yes',close='No'}) if P=="Yes" then aegisub.progress.title("Encoding...") batch=batch:gsub("%=","^=") os.execute(quo(batch)) end end function time2string(num) timecode=math.floor(num/1000) tc0=math.floor(timecode/3600) tc1=math.floor(timecode/60) tc2=timecode%60 numstr="00"..num tc3=numstr:match("(%d%d)%d$") if tc1==60 then tc1=0 tc0=tc0+1 end if tc2==60 then tc2=0 tc1=tc1+1 end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end tc0=tostring(tc0) tc1=tostring(tc1) tc2=tostring(tc2) timestring=tc0..":"..tc1..":"..tc2.."."..tc3 return timestring end function gui(a,b) for k,v in ipairs(GUI) do if b==nil then b="" end if v.name==a then v.value=b else v.value=res[v.name] end end end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function quo(x) x="\""..x.."\"" return x end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end if haveDepCtrl then depRec:registerMacro(encode) else aegisub.register_macro(script_name,script_description,encode) end

-- Disclaimer: RTFM! - http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#cleanup -- If you get unexpected results because you didn't read what the script does, it's your fault. script_name="Script Cleanup" script_description="Removes selected stuff from script" script_author="unanimated" script_version="3.42" script_namespace="ua.ScriptCleanup" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.4.2" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end dont_delete_empty_tags=false -- option to not delete {} function cleanlines(subs,sel) if res.all then for k,v in ipairs(GUI) do if v.x==0 then res[v.name]=true end end end for z,i in ipairs(sel) do progress("Processing line: "..z.."/"..#sel) prog=math.floor(z/#sel*100) aegisub.progress.set(prog) line=subs[i] text=line.text stl=line.style

if res.nots and not res.nocom then text=text:gsub("{TS[^}]*} *","") end

if res.nocom then text=text:gsub("{[^\\}]-}","") :gsub("{[^\\}]-\\N[^\\}]-}","") :gsub("^({[^}]-}) *","%1") :gsub(" *$","") end

if res.clear_a then line.actor="" end if res.clear_e then line.effect="" end

if res.layers and line.layer<5 then if stl:match("Defa") or stl:match("Alt") or stl:match("Main") then line.layer=line.layer+5 end end

if res.cleantag and text:match("{%*?\\") then txt2=text text=text:gsub("{\\\\k0}","") :gsub("{(\\[^}]-)} *\\N *{(\\[^}]-)}","\\N{%1%2}") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 text=text:gsub("({\\[^}]-){(\\[^}]-})","%1%2") :gsub("{.-\\r","{\\r") :gsub("^{\\r([\\}])","{%1") text=text:gsub("\\fad%(0,0%)","") :gsub(ATAG.."$","") for tgs in text:gmatch(ATAG) do tgs2=tgs :gsub("\\([\\}])","%1") :gsub("(\\%a+)([%d%-]+%.%d+)",function(a,b) if not a:match("\\fn") then b=rnd2dec(b) end return a..b end) :gsub("(\\%a+)%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c) b=rnd2dec(b) c=rnd2dec(c) return a.."("..b..","..c..")" end) :gsub("(\\%a+)%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(a,b,c,d,e) b=rnd2dec(b) c=rnd2dec(c) d=rnd2dec(d) e=rnd2dec(e) return a.."("..b..","..c..","..d..","..e end) tgs2=duplikill(tgs2) tgs2=extrakill(tgs2) text=text:gsub(esc(tgs),tgs2) :gsub("^({\\[^}]-)\\frx0\\fry0","%1") end if txt2~=text then kleen=kleen+1 end end

if res.overlap then if line.comment==false and stl:match("Defa") then start=line.start_time endt=line.end_time if i<#subs then nextline=subs[i+1] nextart=nextline.start_time end prevline=subs[i-1] prevstart=prevline.start_time prevend=prevline.end_time dur=line.end_time-line.start_time ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame keyframes=aegisub.keyframes() startf=ms2fr(start) endf=ms2fr(endt) prevendf=ms2fr(prevend) nextartf=ms2fr(nextart)

-- start gaps/overlaps if prevline.class=="dialogue" and prevline.style:match("Defa") and dur>50 then -- get keyframes kfst=0 kfprev=0 for k,kf in ipairs(keyframes) do if kf==startf then kfst=1 end if kf==prevendf then kfprev=1 end end -- start overlap if startprevend and start-prevend<=50 then if kfst==0 and kfprev==1 then nstart=prevend end end end -- end gaps/overlaps if i<#subs and nextline.style:match("Defa") and dur>50 then --get keyframes kfend=0 kfnext=0 for k,kf in ipairs(keyframes) do if kf==endf then kfend=1 end if kf==nextartf then kfnext=1 end end -- end overlap if endt>nextart and endt-nextart<=50 then if kfnext==1 and kfend==0 then nendt=nextart end end -- end gap if endt

text=text:gsub("^ *","") :gsub("\\t%([^\\%)]-%)","") :gsub("{%*}","") if not dont_delete_empty_tags then text=text:gsub("{}","") end if line.text~=text then chng=chng+1 end line.text=text subs[i]=line end if res.info then infotxt="Lines with modified Text field: "..chng if res.cleantag then infotxt=infotxt.."\nChanges from Clean Tags: "..kleen end P,rez=ADD({{class="label",label=infotxt}},{"OK"},{close='OK'}) end aegisub.set_undo_point(script_name) return sel end function rnd2dec(num) num=math.floor((num*100)+0.5)/100 return num end

-- delete commented lines from selected lines function nocom_line(subs,sel) progress("Deleting commented lines") ncl_sel={} for s=#sel,1,-1 do line=subs[sel[s]] if line.comment then for z,i in ipairs(ncl_sel) do ncl_sel[z]=i-1 end subs.delete(sel[s]) else table.insert(ncl_sel,sel[s]) end end return ncl_sel end

-- delete empty lines function noempty(subs,sel) progress("Deleting empty lines") noe_sel={} for s=#sel,1,-1 do line=subs[sel[s]] if line.text=="" then for z,i in ipairs(noe_sel) do noe_sel[z]=i-1 end subs.delete(sel[s]) else table.insert(noe_sel,sel[s]) end end return noe_sel end

-- delete commented or empty lines function noemptycom(subs,sel) progress("Deleting commented/empty lines") noecom_sel={} for s=#sel,1,-1 do line=subs[sel[s]] if line.comment or line.text=="" then for z,i in ipairs(noecom_sel) do noecom_sel[z]=i-1 end subs.delete(sel[s]) else table.insert(noecom_sel,sel[s]) end end return noecom_sel end

-- delete unused styles function nostyle(subs,sel) stylist=",," for i=#subs,1,-1 do if subs[i].class=="dialogue" then line=subs[i] text=line.text st2=text:match("\\r([^\\}]*)") st=line.style if not stylist:match(","..esc(st)..",") then stylist=stylist..st..",," end if st2 and st2~="" and not stylist:match(","..esc(st2)..",") then stylist=stylist..st2..",," end end if subs[i].class=="style" then style=subs[i] if res.nostyle2 and style.name:match("Defa") or res.nostyle2 and style.name:match("Alt") then nodel=1 else nodel=0 end if not stylist:match(","..esc(style.name)..",") and nodel==0 then subs.delete(i) logg("\n Deleted style: "..style.name) for s=1,#sel do sel[s]=sel[s]-1 end end end end return sel end

-- switch true/false function ft(x) if x then return false else return true end end

-- kill everything function killemall(subs,sel) if res.inverse then for k,v in ipairs(GUI) do if v.x>3 and v.y>0 and v.name~="onlyt" then res[v.name]=ft(res[v.name]) end end end for z,i in ipairs(sel) do progress("Processing line: "..z.."/"..#sel) line=subs[i] text=line.text if res.onlyt then res.trans=false text=text:gsub(ATAG,function(t) return t:gsub("\\","|") end) :gsub("|t(%b())",function(t) return "\\t"..t:gsub("|","\\") end) end tags=text:match(STAG) or "" inline=text:gsub(STAG,"") if res.skill and res.ikill then trgt=text tg=3 elseif res.ikill then trgt=inline tg=2 else trgt=tags tg=1 end if res.border then trgt=killtag("[xy]?bord",trgt) end if res.shadow then trgt=killtag("[xy]?shad",trgt) end if res.blur then trgt=killtag("blur",trgt) end if res.bee then trgt=killtag("be",trgt) end if res.fsize then trgt=killtag("fs",trgt) end if res.fspace then trgt=killtag("fsp",trgt) end if res.scalex then trgt=killtag("fscx",trgt) end if res.scaley then trgt=killtag("fscy",trgt) end if res.fade then trgt=trgt:gsub("\\fade?%b()","") end if res.posi then trgt=trgt:gsub("\\pos%b()","") end if res.move then trgt=trgt:gsub("\\move%b()","") end if res.org then trgt=trgt:gsub("\\org%b()","") end if res.color1 then trgt=killctag("1?c",trgt) end if res.color2 then trgt=killctag("2c",trgt) end if res.color3 then trgt=killctag("3c",trgt) end if res.color4 then trgt=killctag("4c",trgt) end if res.alfa1 then trgt=killctag("1a",trgt) end if res.alfa2 then trgt=killctag("2a",trgt) end if res.alfa3 then trgt=killctag("3a",trgt) end if res.alfa4 then trgt=killctag("4a",trgt) end if res.alpha then trgt=killctag("alpha",trgt) end if res.clip then trgt=trgt:gsub("\\i?clip%b()","") end if res.fname then trgt=trgt:gsub("\\fn[^\\}]+","") end if res.frz then trgt=killtag("frz",trgt) end if res.frx then trgt=killtag("frx",trgt) end if res.fry then trgt=killtag("fry",trgt) end if res.fax then trgt=killtag("fax",trgt) end if res.fay then trgt=killtag("fay",trgt) end if res.anna then trgt=killtag("an",trgt) end if res.align then trgt=killtag("a",trgt) end if res.wrap then trgt=killtag("q",trgt) end if res["return"] then trgt=trgt:gsub("\\r.+([\\}])","%1") end if res.kara then trgt=trgt:gsub("\\[Kk][fo]?[%d%.]+([\\}])","%1") end if res.ital then repeat trgt,r=trgt:gsub("\\i[01]?([\\}])","%1") until r==0 end if res.bold then repeat trgt,r=trgt:gsub("\\b[01]?([\\}])","%1") until r==0 end if res.under then repeat trgt,r=trgt:gsub("\\u[01]?([\\}])","%1") until r==0 end if res.stri then repeat trgt,r=trgt:gsub("\\s[01]?([\\}])","%1") until r==0 end if res.trans then trgt=trgt:gsub("\\t%b()","") end trgt=trgt:gsub("\\t%([%d%.,]*%)","") :gsub("{%**}","") if tg==1 then tags=trgt elseif tg==2 then inline=trgt elseif tg==3 then text=trgt end if trgt~=text then text=tags..inline end if res.onlyt then text=text:gsub("{%*?|[^}]-}",function(t) return t:gsub("|","\\") end) end line.text=text subs[i]=line end end function killtag(tag,t) t=t:gsub("\\"..tag.."[%d%.%-]*([\\}])","%1") return t end function killctag(tag,t) t=t:gsub("\\"..tag.."&H%x+&","") repeat t,r=t:gsub("\\"..tag.."([\\}])","%1") until r==0 return t end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function cleantr(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..trnsfrm.."}") return tags end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function cleanup(subs,sel,act) ADD=aegisub.dialog.display ak=aegisub.cancel if #sel==0 then t_error("No selection",1) end ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" if act==0 then act=sel[1] end chng=0 kleen=0 GUI={ {x=0,y=0,class="checkbox",name="nots",label="Remove TS timecodes",hint="Removes timecodes like {TS 12:36}"}, {x=0,y=1,class="checkbox",name="nocom",label="Remove comments from lines",hint="Removes {comments} (not tags)"}, {x=0,y=2,class="checkbox",name="clear_a",label="Clear Actor field"}, {x=0,y=3,class="checkbox",name="clear_e",label="Clear Effect field"}, {x=0,y=4,class="checkbox",name="layers",label="Raise dialogue layer by 5"}, {x=0,y=5,class="checkbox",name="cleantag",label="Clean up tags",hint="Fixes duplicates, \\\\, \\}, }{, and other garbage"}, {x=0,y=6,class="checkbox",name="overlap",label="Fix 1-frame gaps/overlaps"}, {x=0,y=7,class="checkbox",name="nocomline",label="Delete commented lines"}, {x=0,y=8,class="checkbox",name="noempty",label="Delete empty lines"}, {x=0,y=9,class="checkbox",name="alphacol",label="Try to fix alpha / colour tags"}, {x=0,y=10,class="checkbox",name="spaces",label="Fix start/end/double spaces"}, {x=0,y=12,class="checkbox",name="info",label="Print info"}, {x=0,y=13,class="checkbox",name="all",label="ALL OF THE ABOVE"},

{x=2,y=0,class="checkbox",name="allcol",label="Remove all colour tags"}, {x=2,y=1,class="checkbox",name="allphas",label="Remove all alpha tags"}, {x=2,y=2,class="checkbox",name="allrot",label="Remove all rotations"}, {x=2,y=3,class="checkbox",name="allpers",label="Remove all perspective"}, {x=2,y=4,class="checkbox",name="allsize",label="Remove size/scaling"}, {x=2,y=5,class="checkbox",name="nobreak",label="Remove linebreaks - \\N"}, {x=2,y=6,class="checkbox",name="nobreak2",label="Remove linebreaks - \\N (nospace)"}, {x=2,y=7,class="checkbox",name="hspace",label="Remove hard spaces - \\h"}, {x=2,y=8,class="checkbox",name="nostyle",label="Delete unused styles"}, {x=2,y=9,class="checkbox",name="nostyle2",label="Delete unused styles (leave Default)"}, {x=2,y=10,class="checkbox",name="ctrans",label="Move transforms to end of tag block"}, {x=2,y=11,class="checkbox",name="inline",label="Remove inline tags"}, {x=2,y=12,class="checkbox",name="inline2",label="Remove inline tags except the last"}, {x=2,y=13,class="checkbox",name="notag",label="Remove all {\\tags} from selected lines "},

{x=3,y=0,height=14,class="label",label="| \n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|"},

{x=4,y=0,class="checkbox",name="skill",label="[start]",value=true}, {x=5,y=0,class="checkbox",name="ikill",label="[inline]",value=true}, {x=6,y=0,class="checkbox",name="inverse",label="[inverse]",hint="kill all except checked ones"}, {x=6,y=1,class="checkbox",name="onlyt",label="[from \\t]",hint="remove only from transforms"},

{x=4,y=1,class="checkbox",name="blur",label="blur"}, {x=4,y=2,class="checkbox",name="border",label="bord",hint="includes xbord and ybord"}, {x=4,y=3,class="checkbox",name="shadow",label="shad",hint="includes xshad and yshad"}, {x=4,y=4,class="checkbox",name="fsize",label="fs"}, {x=4,y=5,class="checkbox",name="fspace",label="fsp"}, {x=4,y=6,class="checkbox",name="scalex",label="fscx"}, {x=4,y=7,class="checkbox",name="scaley",label="fscy"}, {x=4,y=8,class="checkbox",name="fname",label="fn"}, {x=4,y=9,class="checkbox",name="ital",label="i"}, {x=4,y=10,class="checkbox",name="bold",label="b"}, {x=4,y=11,class="checkbox",name="under",label="u"}, {x=4,y=12,class="checkbox",name="stri",label="s"}, {x=4,y=13,class="checkbox",name="wrap",label="q"},

{x=5,y=1,class="checkbox",name="bee",label="be"}, {x=5,y=2,class="checkbox",name="color1",label="c, 1c"}, {x=5,y=3,class="checkbox",name="color2",label="2c"}, {x=5,y=4,class="checkbox",name="color3",label="3c"}, {x=5,y=5,class="checkbox",name="color4",label="4c"}, {x=5,y=6,class="checkbox",name="alpha",label="alpha"}, {x=5,y=7,class="checkbox",name="alfa1",label="1a"}, {x=5,y=8,class="checkbox",name="alfa2",label="2a"}, {x=5,y=9,class="checkbox",name="alfa3",label="3a"}, {x=5,y=10,class="checkbox",name="alfa4",label="4a"}, {x=5,y=11,class="checkbox",name="align",label="a"}, {x=5,y=12,class="checkbox",name="anna",label="an"}, {x=5,y=13,class="checkbox",name="clip",label="(i)clip "},

{x=6,y=2,class="checkbox",name="fade",label="fad"}, {x=6,y=3,class="checkbox",name="posi",label="pos"}, {x=6,y=4,class="checkbox",name="move",label="move"}, {x=6,y=5,class="checkbox",name="org",label="org"}, {x=6,y=6,class="checkbox",name="frz",label="frz"}, {x=6,y=7,class="checkbox",name="frx",label="frx"}, {x=6,y=8,class="checkbox",name="fry",label="fry"}, {x=6,y=9,class="checkbox",name="fax",label="fax"}, {x=6,y=10,class="checkbox",name="fay",label="fay"}, {x=6,y=11,class="checkbox",name="return",label="r"}, {x=6,y=12,class="checkbox",name="kara",label="k/kf/ko"}, {x=6,y=13,class="checkbox",name="trans",label="t"} } P,res=ADD(GUI, {"Run selected","Comments","Tags","Dial 5","Clean Tags","^ Kill checked tags","Cancer"},{ok='Run selected',cancel='Cancer'}) if P=="Cancer" then ak() end if P=="^ Kill checked tags" then killemall(subs,sel) end if P=="Comments" then res.nocom=true cleanlines(subs,sel) end if P=="Tags" then res.notag=true cleanlines(subs,sel) end if P=="Dial 5" then res.layers=true cleanlines(subs,sel) end if P=="Clean Tags" then res.cleantag=true cleanlines(subs,sel) end if P=="Run selected" then C=0 for key,v in ipairs(GUI) do if v.x==0 and res[v.name] or v.x==2 and res[v.name] then C=1 end end if C==0 then t_error("Run Selected: Error - nothing selected",1) end if res.all then for key,v in ipairs(GUI) do if v.x==2 then res[v.name]=false end end cleanlines(subs,sel) sel=noemptycom(subs,sel) else cleanlines(subs,sel) if res.nocomline and res.noempty then sel=noemptycom(subs,sel) else if res.nocomline then sel=nocom_line(subs,sel) end if res.noempty then sel=noempty(subs,sel) end end table.sort(sel) if res.nostyle or res.nostyle2 then sel=nostyle(subs,sel) end end end if act>#subs then act=#subs end aegisub.set_undo_point(script_name) return sel,act end if haveDepCtrl then depRec:registerMacro(cleanup) else aegisub.register_macro(script_name,script_description,cleanup) end -- Times a sign with {TS 3:24} to 3:24-3:25. Can convert and use a few other formats, like {3:24}, {TS,3:24}, {3,24} etc. -- supported timecodes: {TS 1:23}; {TS 1:23 words}; {TS words 1:23}; {TS,1:23}; {1:23}; {1;23}; {1,23}; {1.23}; [1:23] and variations script_name="Time signs from timecodes" script_description="Rough-times signs from TS timecodes" script_author="unanimated" script_version="2.7" function signtime(subs,sel) for i=#sel,1,-1 do line=subs[sel[i]] text=line.text -- format timecodes text=text :gsub("({.-})({TS.-})","%2%1") :gsub("(%d+):(%d%d):(%d%d)", function(a,b,c) return a*60+b..":"..c end) -- hours :gsub("^(%d%d%d%d)%s%s*","{TS %1}") -- ^1234 text :gsub("(%d%d)ish","%1") -- 1:23ish :gsub("^([%d%s:,%-]+)","{%1}") -- ^1:23, 2:34, 4:56 :gsub("^(%d+:%d%d)%s%-%s","{TS %1}") -- ^12?:34 - :gsub("^(%d+:%d%d%s*)","{TS %1}") -- ^12?:34 :gsub("^{(%d+:%d%d)","{TS %1") -- ^{12?:34 :gsub("^%[(%d+:%d%d)%]:?%s*","{TS %1}") -- ^[12?:34]:? :gsub("{TS[%s%p]+(%d)","{TS %1") -- {TS ~12:34 :gsub("({[^}]-)(%d+)[%;%.,]?(%d%d)([^:%d][^}]-})","%1%2:%3%4") -- {1;23 / 1.23 / 1,23 123} :gsub("{TS%s([^%d\\}]+)%s(%d+:%d%d)","{TS %2 %1") -- {TS comment 12:34} :gsub(":%s?}","}") -- {TS 12:34: } :gsub("|","\\N") tc=text:match("^{[^}]-}") or "" tc=tc:gsub("(%d+)(%d%d)([^:])","%1:%2%3") text=text:gsub("^{[^}]-}%s*",tc) if res.blur then text=text:gsub("\\blur[%d%.]+",""):gsub("{}",""):gsub("^","{\\blur"..res.bl.."}") end line.text=text

tstags=text:match("{TS[^}]-}") or ""

times={} -- collect times if there are more for tag in tstags:gmatch("%d+:%d+") do table.insert(times,tag) end

for t=#times,1,-1 do tim=times[t] -- convert to start time tstid1,tstid2=tim:match("(%d+):(%d%d)") if tstid1 then tid=(tstid1*60000+tstid2*1000-500) end -- shifting times if tid then if res.shift then tid=tid+res.secs*1000 end -- set start and end time [500ms before and after the timecode] line.start_time=tid line.end_time=(tid+1000) end -- snapping to keyframes if res.snap then start=line.start_time endt=line.end_time startf=ms2fr(start) endf=ms2fr(endt) diff=250 diffe=250 startkf=keyframes[1] endkf=keyframes[#keyframes]

-- check for nearby keyframes for k,kf in ipairs(keyframes) do -- startframe snap up to 24 frames back [scroll down to change default] and 5 frames forward if kf>=startf-res.kfs and kf=endf-10 and kf<=endf+res.kfe then tdiff=math.abs(endf-kf) if tdiff1 then table.insert(sel,sel[#sel]+1) end end if #times>0 then subs.delete(sel[i]) end end if res.copy then taim=0 tame=0 for z,i in ipairs(sel) do l=subs[i] if l.start_time==0 then l.start_time=taim l.end_time=tame end taim=l.start_time tame=l.end_time subs[i]=l end end return sel end

-- Config Stuff -- function saveconfig() savecfg="Time Signs Settings\n\n" for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="intedit" or val.class=="checkbox" and val.name~="save" then savecfg=savecfg..val.name..":"..tf(res[val.name]).."\n" end end file=io.open(cfgpath,"w") file:write(savecfg) file:close() ADD({{class="label",label="Config saved to:\n"..cfgpath}},{"OK"},{close='OK'}) end function loadconfig() cfgpath=aegisub.decode_path("?user").."\\timesigns.conf" file=io.open(cfgpath) if file~=nil then konf=file:read("*all") file:close() for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="intedit" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function timesigns(subs,sel) ADD=aegisub.dialog.display GUI={ {x=0,y=0,width=4,class="label",label="Check this if all your timecodes are too late or early:"}, {x=0,y=1,width=1,class="checkbox",name="shift",label="Shift timecodes by "}, {x=1,y=1,width=2,class="floatedit",name="secs",value=-10,hint="Negative=backward / positive=forward",step=0.5}, {x=3,y=1,width=1,class="label",label=" sec."}, {x=0,y=2,width=4,class="checkbox",name="copy",label="For lines without timecodes, copy from the previous line"}, {x=0,y=3,width=4,class="checkbox",name="nots",label="Automatically remove {TS ...} comments"}, {x=0,y=4,width=2,class="checkbox",name="blur",label="Automatically add blur:"}, {x=2,y=4,width=2,class="floatedit",name="bl",value="0.6"}, {x=0,y=5,width=2,class="checkbox",name="snap",label="Snapping to keyframes:",value=true}, {x=0,y=6,width=2,class="label",label="Frames to search back:"}, {x=0,y=7,width=2,class="label",label="Frames to search forward:"}, {x=2,y=6,width=2,class="intedit",name="kfs",value="24",step=1,min=1,max=250}, {x=2,y=7,width=2,class="intedit",name="kfe",value="24",step=1,min=1,max=250}, {x=0,y=8,width=2,class="checkbox",name="save",label="Save current settings"}, {x=2,y=8,width=2,class="label",label=" [Time Signs v"..script_version.."]"}, } loadconfig() buttons={"No more suffering with SHAFT signs!","Exit"} P,res=ADD(GUI,buttons,{ok='No more suffering with SHAFT signs!',cancel='Exit'}) if P=="Exit" then aegisub.cancel() end ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame keyframes=aegisub.keyframes() if res.save then saveconfig() end if P=="No more suffering with SHAFT signs!" then sel=signtime(subs,sel) end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name,script_description,timesigns) -- This is meant to get you to the "next sign" in the subtitle grid. (or previous) -- When mocha-tracking over 1000 lines, it can be a pain in the ass to find where one sign ends and another begins. -- Select lines that belong to the current "sign", i.e. all different layers/masks/texts. -- The script will search from there for the first line in the grid that doesn't match any of the selected ones with "text", "style", etc. script_name="Jump to Next" script_description="Jumps to next 'sign' in the subtitle grid" script_description2="Jumps to previous 'sign' in the subtitle grid" script_author="unanimated" script_version="2.0" script_namespace="ua.JumpToNext" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end ak=aegisub.cancel function nextsel(subs,sel) getinfo(subs,sel) if j==#subs then ak() end count=1 repeat line=subs[j+count] markers() ch=0 for m=1,#marks do if marks[m]==hit then ch=1 end end if ch==0 or j+count==#subs then sel={j+count} end count=count+1 until ch==0 or hit==nil or j+count>#subs return sel end function prevsel(subs,sel) getinfo(subs,sel) if subs[i-1].class~="dialogue" then ak() end count=1 repeat line=subs[i-count] markers() ch=0 for m=1,#marks do if marks[m]==hit then ch=1 end end if ch==0 or subs[i-count-1].class~="dialogue" then sel={i-count} end count=count+1 until ch==0 or hit==nil or subs[i-count].class~="dialogue" return sel end function getinfo(subs,sel) lm=nil i=sel[1] j=sel[#sel] marks={} for z,i in ipairs(sel) do rine=subs[i] if marker=="text" then mark=rine.text:gsub("%b{}","") end if marker=="style" then mark=rine.style end if marker=="actor" then mark=rine.actor end if marker=="effect" then mark=rine.effect end if marker=="layer" then mark=rine.layer end if mark~=lm then table.insert(marks,mark) end lm=mark end end function markers() if marker=="text" then hit=line.text:gsub("%b{}","") end if marker=="style" then hit=line.style end if marker=="actor" then hit=line.actor end if marker=="effect" then hit=line.effect end if marker=="layer" then hit=line.layer end end function nextcom(subs,sel) j=sel[#sel] if j==#subs then ak() end repeat j=j+1 line=subs[j] if j==#subs then sel={j} end if line.comment then sel={j} end until line.comment or j==#subs return sel end function prevcom(subs,sel) i=sel[1] repeat i=i-1 line=subs[i] if line.class~="dialogue" then sel={i+1} end if line.comment then sel={i} end until line.comment or line.class~="dialogue" return sel end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function nextT(subs,sel) marker="text" sel=nextsel(subs,sel) return sel end function nextS(subs,sel) marker="style" sel=nextsel(subs,sel) return sel end function nextA(subs,sel) marker="actor" sel=nextsel(subs,sel) return sel end function nextE(subs,sel) marker="effect" sel=nextsel(subs,sel) return sel end function nextL(subs,sel) marker="layer" sel=nextsel(subs,sel) return sel end function nextC(subs,sel) sel=nextcom(subs,sel) return sel end function prevT(subs,sel) marker="text" sel=prevsel(subs,sel) return sel end function prevS(subs,sel) marker="style" sel=prevsel(subs,sel) return sel end function prevA(subs,sel) marker="actor" sel=prevsel(subs,sel) return sel end function prevE(subs,sel) marker="effect" sel=prevsel(subs,sel) return sel end function prevL(subs,sel) marker="layer" sel=prevsel(subs,sel) return sel end function prevC(subs,sel) sel=prevcom(subs,sel) return sel end function nextG(subs,sel) GUI={{class="label",label="Jump to Next..."},{x=1,class="checkbox",name="prev",label="Jump to Previous"}} P,res=aegisub.dialog.display(GUI,{"Text","Style","Actor","Effect","Layer","Comment","X"},{ok='Text',close='X'}) if P=="X" then ak() end if res.prev then if P=="Text" then marker="text" sel=prevsel(subs,sel) end if P=="Style" then marker="style" sel=prevsel(subs,sel) end if P=="Actor" then marker="actor" sel=prevsel(subs,sel) end if P=="Effect" then marker="effect" sel=prevsel(subs,sel) end if P=="Layer" then marker="layer" sel=prevsel(subs,sel) end if P=="Comment" then sel=prevcom(subs,sel) end else if P=="Text" then marker="text" sel=nextsel(subs,sel) end if P=="Style" then marker="style" sel=nextsel(subs,sel) end if P=="Actor" then marker="actor" sel=nextsel(subs,sel) end if P=="Effect" then marker="effect" sel=nextsel(subs,sel) end if P=="Layer" then marker="layer" sel=nextsel(subs,sel) end if P=="Comment" then sel=nextcom(subs,sel) end end return sel end if haveDepCtrl then depRec:registerMacros({ {"Jump to Next/_GUI",script_description,nextG}, {"Jump to Next/Text",script_description,nextT}, {"Jump to Next/Style",script_description,nextS}, {"Jump to Next/Actor",script_description,nextA}, {"Jump to Next/Effect",script_description,nextE}, {"Jump to Next/Layer",script_description,nextL}, {"Jump to Next/Commented Line",script_description,nextC}, {"Jump to Previous/Text",script_description2,prevT}, {"Jump to Previous/Style",script_description2,prevS}, {"Jump to Previous/Actor",script_description2,prevA}, {"Jump to Previous/Effect",script_description2,prevE}, {"Jump to Previous/Layer",script_description2,prevL}, {"Jump to Previous/Commented Line",script_description2,prevC}, },false) else aegisub.register_macro("Jump to Next/_GUI",script_description,nextG) aegisub.register_macro("Jump to Next/Text",script_description,nextT) aegisub.register_macro("Jump to Next/Style",script_description,nextS) aegisub.register_macro("Jump to Next/Actor",script_description,nextA) aegisub.register_macro("Jump to Next/Effect",script_description,nextE) aegisub.register_macro("Jump to Next/Layer",script_description,nextL) aegisub.register_macro("Jump to Next/Commented Line",script_description,nextC) aegisub.register_macro("Jump to Previous/Text",script_description2,prevT) aegisub.register_macro("Jump to Previous/Style",script_description2,prevS) aegisub.register_macro("Jump to Previous/Actor",script_description2,prevA) aegisub.register_macro("Jump to Previous/Effect",script_description2,prevE) aegisub.register_macro("Jump to Previous/Layer",script_description2,prevL) aegisub.register_macro("Jump to Previous/Commented Line",script_description2,prevC) end --A string of mixed letters and numbers my_string="a1b2c3d4e5f6g7"

--%a matches letters only, %d matches digits only, so we'll see what we can make gsub do --using those.

--First, a straightforward substitution of text. This turns all letters to exclamation points new_string1=my_string:gsub("%a","!") print(new_string1)

--Now let's use captures to show how we can save data from the original line and use it --This surrounds the letters in brackets, while preserving what the letters were new_string2=my_string:gsub("(%a)","[%1]") print(new_string2)

--Now we can use multiple captures and rearrange them --This will capture a letter-number-letter-number sequence, and swap the letters new_string3=my_string:gsub("(%a)(%d)(%a)(%d)","%3%2%1%4") print(new_string3)

--Finally, we can send these captures to a function --This function will take four arguments. It uppercases the first and third arguments, --swaps the second and fourth arguments, and returns a string with them put together in that order function switcheroo(arg1,arg2,arg3,arg4) return arg1:upper()..arg4..arg3:upper()..arg2 end

--Now when we use that function in gsub, it receives whatever arguments are passed to it from the --pattern. The pattern matches and captures a letter, a digit, a letter, a digit. On our test string, --it will capture a, 1, b, and 2. So gsub will run switcheroo("a","1","b","2"). gsub doesn't know what --switcheroo does, or what the arguments are named. It just knows to give the results of the capture --to the function. The function switcheroo then interprets those results as its arg1, arg2, arg3, and arg4. --The result of switcheroo is given to gsub, and gsub replaces "a1b2" with "A2B1" new_string4=my_string:gsub("(%a)(%d)(%a)(%d)",switcheroo) print(new_string4)

--You can also define the switcheroo function anonymously, like this: new_string5=my_string:gsub("(%a)(%d)(%a)(%d)", function(letter1, number1, letter2, number2) --Uppercase the letters, swap the numbers return letter1:upper()..number2..letter2:upper()..number1 end) print(new_string5) --[[ README:

Put some explanation about your script at the top! People should know what it does and how to use it. ]]

--Define some properties about your script script_name="Name of script" script_description="What it does" script_author="You" script_version="1.0" --To make sure you and your users know which version is newest

--This is the main processing function that modifies the subtitles function macro_function(subtitle, selected, active) --Code your function here aegisub.set_undo_point(script_name) --Automatic in 3.0 and above, but do it anyway return selected --This will preserve your selection (explanation below) end

--This optional function lets you prevent the user from running the macro on bad input function macro_validation(subtitle, selected, active) --Check if the user has selected valid lines --If so, return true. Otherwise, return false return true end

--This is what puts your automation in Aegisub's automation list aegisub.register_macro(script_name,script_description,macro_function,macro_validation) function do_for_selected(sub, sel, act) --Keep in mind that si is the index in sel, while li is the line number in sub for si,li in ipairs(sel) do --Read in the line line = sub[li]

--Do stuff to line here

--Put the line back in the subtitles sub[li] = line end aegisub.set_undo_point(script_name) return sel end --[[ README:

Add Italics

Makes all lines in the selection italic. ]]

--Script properties script_name="Make italic" script_description="Italicizes selected lines" script_author="lyger" script_version="1.0"

--Main processing function function italicize(sub, sel)

--Go through all the lines in the selection for si,li in ipairs(sel) do

--Read in the line local line=sub[li]

--Add the italics. Don't forget to escape the slash line.text="{\\i1}"..line.text

--Put the line back into the subtitles sub[li]=line

end

--Set undo point and maintain selection aegisub.set_undo_point(script_name) return sel end

--Register macro (no validation function required) aegisub.register_macro(script_name,script_description,italicize) --[[ README:

Refactor Font Size for Motion Tracking (simple)

Because vsfilter does not support non-whole-number scaling, motion-tracked scaling often looks jerky. One solution is to decrease the font size and increase the scaling, so that the text appears the same size, but the scaling can be more precise.

For example, \fs50 would become \fs5\fscx1000\fscy1000. The text appears the same size, but the scale statements have four digits of precision, instead of three.

This script provides a limited solution to automating this process. It will only work on lines that contain an \fs tag in the line itself but do not contain any \fscx or \fscy tags. The line must also have an override block at the beginning. A full solution that properly handles exceptional cases will be provided in the advanced examples section.

]]

--Script properties script_name="Refactor font size (simple)" script_description="Increases scaling precision by decreasing font size" script_author="lyger" script_version="1.0"

--Main processing function function refont(sub, sel)

--Go through all the lines in the selection for si,li in ipairs(sel) do

--Read in the line local line=sub[li]

--Use gsub to replace "\fs" tags with a set of recalculated tags line.text=line.text:gsub("\\fs(%d+)", --The second argument to gsub is this anonymous function --See the gsub example script if you are confused function(size)

--Convert to number size=tonumber(size)

--Divide by ten and truncate to get the new font size newsize=math.floor(size/10)

--The percent by which to scale the text up, to correct for the decreased font size newscale=math.floor( 100 * (size / newsize) )

--Use a format string to neatly generate a new set of tags newtags=string.format("\\fs%d\\fscx%d\\fscy%d",newsize,newscale,newscale)

--Return these new tags --gsub will replace the old tags with these return newtags

end)

--Put the line back into the subtitles sub[li]=line

end

--Set undo point and maintain selection aegisub.set_undo_point(script_name) return sel end

--Validation function for the script function refont_validate(sub,sel) --Check every line in the selection for si,li in ipairs(sel) do

--If one of them does not have a "\fs" tag in it, then this script cannot run, so return false if sub[li].text:match("\\fs") == nil then return false end

end

--Otherwise, return true return true end

--Register macro aegisub.register_macro(script_name,script_description,refont,refont_validate) --[[ README:

Refactor Font Size for Motion Tracking (advanced)

Because vsfilter does not support non-whole-number scaling, motion-tracked scaling often looks jerky. One solution is to decrease the font size and increase the scaling, so that the text appears the same size, but the scaling can be more precise.

For example, \fs50 would become \fs5\fscx1000\fscy1000. The text appears the same size, but the scale statements have four digits of precision, instead of three.

This script provides a robust solution that handles most cases, including lines that do not define font size in-line, varied \fscx and \fscy tags, and lines without tags at the beginning.

]]

--Script properties script_name="Refactor font size (advanced)" script_description="Increases scaling precision by decreasing font size" script_author="lyger" script_version="1.0" include("karaskel.lua")

--Main processing function function refont(sub, sel)

--Collect metadata local meta,styles=karaskel.collect_head(sub,false)

--Go through all the lines in the selection for si,li in ipairs(sel) do

--Read in the line local line=sub[li]

--Preprocess karaskel.preproc_line(sub,meta,styles,line)

--The next few steps will break the line up into tag-text pairs

--First ensures that the line begins with an override tag block --x = A and B or C means if A, then x = B, else x = C ltext=(line.text:match("^{")==nil) and "{}"..line.text or line.text

--Then ensure that the first tag includes an \fs tag if ltext:match("^{[^}]*\\fs%d+[^}]*}")==nil then ltext=ltext:gsub("^{",string.format("{\\fs%d",line.styleref.fontsize)) end

--To ensure that the gmatch works, temporarily insert a \t character after --each closed curly brace, otherwise adjacent override blocks with no text --in between won't match --Yes, this is kind of hacky ltext=ltext:gsub("}","}\t")

--Match for pairs of tags followed by text using the pattern "({[^}]*})([^{]*)" --Store these pairs in a new data structure tt_table={} for tg,tx in ltext:gmatch("({[^}]*})([^{]*)") do table.insert(tt_table,{tag=tg,text=tx:gsub("^\t","")})--Remove the \t we inserted end

--See tutorial for a visual of what this new data structure looks like

--These store the current values of the three parameters at the part of the line --we are looking at --Since we have not started looking at the line yet, these are set to the style defaults cur_fs=line.styleref.fontsize cur_fscx=line.styleref.scale_x cur_fscy=line.styleref.scale_y

--This is where the new text will be stored rebuilt_text=""

--Now rebuild the line piece-by-piece using the tag-text pairs stored in tt_table for _,tt in ipairs(tt_table) do --Set the current values to the tag overrides if they exist, otherwise keep values --x = A or B means x = A if A is not nil, otherwise x = B cur_fs=tonumber(tt.tag:match("\\fs([%d%.]+)")) or cur_fs cur_fscx=tonumber(tt.tag:match("\\fscx([%d%.]+)")) or cur_fscx cur_fscy=tonumber(tt.tag:match("\\fscy([%d%.]+)")) or cur_fscy

--Use similar math as we did in the "simple" script to recalculate values --If the font size is less than 10, we cannot divide by 10 and floor or we'll --get a font size of zero, so use this to limit font size to at least 1 new_fs=(cur_fs>10) and math.floor(cur_fs/10) or 1

--Scale factor factor=cur_fs/new_fs

--New scales new_fscx=math.floor(cur_fscx*factor) new_fscy=math.floor(cur_fscy*factor)

--If an \fs tag is present if tt.tag:match("\\fs[%d%.]+")~=nil then

--Remove any \fscx and \fscy tags tt.tag=tt.tag:gsub("\\fscx[%d%.]+","") tt.tag=tt.tag:gsub("\\fscx[%d%.]+","")

--Sub in the new font size and scale tags tt.tag=tt.tag:gsub("\\fs[%d%.]+", string.format("\\fs%d\\fscx%d\\fscy%d",new_fs,new_fscx,new_fscy))

else --If \fs is not present, then substitute and \fscx and \fscy that are there tt.tag=tt.tag:gsub("\\fscx[%d%.]+","\\fscx"..new_fscx) tt.tag=tt.tag:gsub("\\fscy[%d%.]+","\\fscy"..new_fscy) end

--Rebuild line rebuilt_text=rebuilt_text..tt.tag..tt.text end

--Replace the old line text with the rebuilt text line.text=rebuilt_text

--Put the line back into the subtitles sub[li]=line

end

--Set undo point and maintain selection aegisub.set_undo_point(script_name) return sel end

--Register macro aegisub.register_macro(script_name,script_description,refont) Functions for automation scripts for Aegisub.

-- add tag to the end of the initial block of tags tag should be backslash+type+value, eg "\\blur0.6" -- use: text=addtag("\\blur0.6",text)

function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end

-- escape string for use in gsub (by lyger) use: string=esc(string)

function esc(str) str=str :gsub("%%","%%%%") :gsub("%(","%%%(") :gsub("%)","%%%)") :gsub("%[","%%%[") :gsub("%]","%%%]") :gsub("%.","%%%.") :gsub("%*","%%%*") :gsub("%-","%%%-") :gsub("%+","%%%+") :gsub("%?","%%%?") return str end

-- select all lines useful when you want the "selected lines/all lines" option -- use it something like this: if res.selection=="all" then sel=selectall(subs, sel) mainfunction(subs, sel) end

function selectall(subs, sel) sel={} for i = 1, #subs do if subs[i].class=="dialogue" then table.insert(sel,i) end end return sel end

-- round a number -- use: number=round(number)

function round(num) num=math.floor(num+0.5) return num end

-- flip text rot can be "frz" "frx" "fry" -- use: text=flip("frz",text)

function flip(rot,text) for rotation in text:gmatch("\\"..rot.."([%d%.%-]+)") do rotation=tonumber(rotation) if rotation<180 then newrot=rotation+180 end if rotation>180 then newrot=rotation-180 end text=text:gsub(rot..rotation,rot..newrot) end return text end

-- clean transforms -- use: text=text:gsub("^({\\[^}]-})",function(tags) return cleantr(tags) end) -- (the longer ones with more %( %) are for transforms with clips)

function cleantr(tags) -- this puts transforms at the end of the block of tags trnsfrm="" for t in tags:gmatch("(\\t%([^%(%)]-%))") do trnsfrm=trnsfrm..t end for t in tags:gmatch("(\\t%([^%(%)]-%([^%)]-%)[^%)]-%))") do trnsfrm=trnsfrm..t end tags=tags:gsub("(\\t%([^%(%)]+%))","") tags=tags:gsub("(\\t%([^%(%)]-%([^%)]-%)[^%)]-%))","") tags=tags:gsub("^({\\[^}]*)}","%1"..trnsfrm.."}") -- this joins transforms with no timecodes together into one - \t(\bord5)\t(\shad5) --> \t(\bord5\shad5) cleant="" for ct in tags:gmatch("\\t%((\\[^%(%)]-)%)") do cleant=cleant..ct end for ct in tags:gmatch("\\t%((\\[^%(%)]-%([^%)]-%)[^%)]-)%)") do cleant=cleant..ct end tags=tags:gsub("(\\t%(\\[^%(%)]+%))","") tags=tags:gsub("(\\t%(\\[^%(%)]-%([^%)]-%)[^%)]-%))","") if cleant~="" then tags=tags:gsub("^({\\[^}]*)}","%1\\t("..cleant..")}") end return tags end

-- clean duplicate tags -- use: text=duplikill(text) -- don't use with \t ! function duplikill(text) tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy" for i=1,#tags1 do tag=tags1[i] text=text:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") end text=text:gsub("\\1c&","\\c&") tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} for i=1,#tags2 do tag=tags2[i] text=text:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2")

--text=text:gsub("\\i?clip%([^%)]-%)([^}]-)(\\i?clip%([^%)]-%))","%1%2")end -- depending whether you wanna allow 2 clips on a line return text end -- this works with \t, but has to be used on tags, not text (and requires esc) --[[ use: for tagz in text:gmatch("{\\[^}]-}") do tagz2=duplikill(tagz) tagz=esc(tagz) text=text:gsub(tagz,tagz2) end --]] function duplikill(tagz) tf="" if tagz:match("\\t") then for t in tagz:gmatch("(\\t%([^%(%)]-%))") do tf=tf..t end for t in tagz:gmatch("(\\t%([^%(%)]-%([^%)]-%)[^%)]-%))","") do tf=tf..t end tagz=tagz:gsub("\\t%([^%(%)]+%)","") tagz=tagz:gsub("\\t%([^%(%)]-%([^%)]-%)[^%)]-%)","") end tags1={"blur","be","bord","shad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay"} for i=1,#tags1 do tag=tags1[i] tagz=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") end tagz=tagz:gsub("\\1c&","\\c&") tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} for i=1,#tags2 do tag=tags2[i] tagz=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") end tagz=tagz:gsub("({\\[^}]-)}","%1"..tf.."}") return tagz end

-- convert a "Dialogue: 0,0:00..." string to a "line" table (uses string2time below) function string2line(str) local ltype,layer,s_time,e_time,style,actor,margl,margr,margv,eff,txt=str:match("(%a+): (%d+),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),(.*)") l2={} l2.class="dialogue" if ltype=="Comment" then l2.comment=true else l2.comment=false end l2.layer=layer l2.start_time=string2time(s_time) l2.end_time=string2time(e_time) l2.style=style l2.actor=actor l2.margin_l=margl l2.margin_r=margr l2.margin_t=margv l2.effect=eff l2.text=txt return l2 end -- convert a line to a "Dialogue: 0,0:00..." string (uses time2string below) (apparently useless because: line.raw) function line2string(lain) if lain.comment==false then ltype="Dialogue: " else ltype="Comment: " end layer=lain.layer.."," s_time=lain.start_time e_time=lain.end_time s_time=time2string(s_time) e_time=time2string(e_time) style=lain.style.."," actor=lain.actor.."," margl=lain.margin_l.."," margr=lain.margin_r.."," margv=lain.margin_t.."," effect=lain.effect.."," txt=lain.text linetext=ltype..layer..s_time..","..e_time..","..style..actor..margl..margr..margv..effect return linetext end

-- convert string timecode to time in ms (and vice versa) function string2time(timecode) timecode=timecode:gsub("(%d):(%d%d):(%d%d)%.(%d%d)",function(a,b,c,d) return d*10+c*1000+b*60000+a*3600000 end) return timecode end function time2string(num) timecode=math.floor(num/1000) tc0=math.floor(timecode/3600) tc1=math.floor(timecode/60) tc2=timecode%60+1 numstr="00"..num tc3=numstr:match("(%d%d)%d$") if tc1==60 then tc1=0 tc0=tc0+1 end if tc2==60 then tc2=0 tc1=tc1+1 end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end tc0=tostring(tc0) tc1=tostring(tc1) tc2=tostring(tc2) timestring=tc0..":"..tc1..":"..tc2.."."..tc3 return timestring end

-- check style values (instead of using karaskel) -- use: styleref=stylechk(subs,line.style) function stylechk(subs,stylename) for i=1, #subs do if subs[i].class=="style" then local st=subs[i] if stylename==st.name then styleref=st end end end return styleref end

Pasted at 11:53:03 EST on 02/06/14, this paste will never expire because it's haxed Format: LUA Italicize script explained

-- italicizes or unitalicizes text [based on style and tags] -- supports multiple \i tags in a line by switching 0 to 1 and vice versa

script_name="Italicize" script_description="Italicizes or unitalicizes text" script_author="unanimated" script_version="1.6" -- let's say for the sake of clarity that we're using this on a line with style "sign"

function italicize(subs, sel) for z, i in ipairs(sel) do local l=subs[i] text=l.text -- this runs function "stylechk" that you see further below -- you pass "l.style" to the function, ie. the name of the style this line is using, in our case "sign" styleref=stylechk(subs,l.style) -- styleref is now kind of like "line", but for the style -- just like you have line.text, you have styleref.name, which is now "sign", or styleref.italic, which is true or false

-- we'll assign the italic element to "si" (style italics) -- it is either true for italics, or false for regular local si=styleref.italic -- now we want to transform true/false into 1/0, creating variable "it" -- the values are 1 for non-italics and 0 for italics, because it's the switched values i want to use if si==false then it="1" else it="0" end -- this is to deal with \i without a number, which resets \i to the style value; i need it to have a number -- "it" is the opposite of the style, so "1-it" is the style value - 1- 0=1; 1-1=0 -- in other words, if style is not italics, we change \i to \i0 text=text:gsub("\\i([\\}])","\\i".. 1-it.."%1") -- now i have to consider 2 options - either there's italics tag at the beginning, or not if text:match("^{[^}]*\\i%d[^}]*}") then -- if there is, then we change it, and all subsequent \i tags, to the opposite value -- i take the number after \i, and change it to 1-number, thus turning 1 to 0 and vice versa text=text:gsub("\\i(%d)", function(num) return "\\i".. 1-num end)

-- this is when there's no \i tag in the first block of tags else -- the line may have italics somewehre in the middle, so we capture the value if text:match("\\i([01])") then italix=text:match("\\i([01])") end -- this may get a bit confusing. we're dealing with a line that has italics in the middle. -- so style value is "not_it" (as in opposite of "it"), value we want for the start of line is "it", -- and value we want for the next \i tag, the one we captured, is again "not_it" -- so logically that value, italix, should be "it" -- if that's the case, all \i tags get switched -- the reason i check for this is that i assume you might be an idiot and have the wrong tag -- in which case i won't change it because it will thus become right if italix==it then text=text:gsub("\\i(%d)", function(num) return "\\i".. 1-num end) end -- we've changed all the existing tags (if there were any), so now we add a tag at the beginning, with the value "it" text="{\\i"..it.."}"..text

-- and join it with other tags at the beginning, if they're there text=text:gsub("{\\i(%d)}({\\[^}]*)}","%2\\i%1}") end l.text=text subs[i]=l end aegisub.set_undo_point(script_name) return sel end -- function to read the style of the current line -- it got called with styleref=stylechk(subs,l.style), therefore given 2 objects - the whole subtitle object, and a style -- here we have stylechk(subs,stylename) - i use different names here, but that doesn't matter - subs stays, l.style becomes stylename -- l.style was "sign", so inside this function, stylename is now "sign" function stylechk(subs,stylename) for i=1, #subs do

-- we're going through styles, looking for the one called "sign" if subs[i].class=="style" then

-- this is pretty much the same thing you do with "line" style=subs[i] -- style.name is the name of the style. with every "i" we're going through another style. -- stylename will match style.name when we get to the "sign" style if stylename==style.name then

-- now we have style "sign", and we copy it to a variable "styleref" styleref=style end end end -- this is what we're returning as the result of this function, so when we used styleref=stylechk(subs,l.style) above, -- we're passing this styleref, which is subs[i] for the "sign" style, to the styleref in the main function -- so styleref is now basically this "Style: sign,Arial,40,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,5,10,10,1

return styleref end aegisub.register_macro(script_name, script_description, italicize) Script Updates (read the manuals for more details and how to use)

Note: 0.01 updates are mostly bugfixes or minor changes. 0.1 updates are usually new features or bigger changes.

2016-09-13 v3.42 Script Cleanup - 'delete unused styles' checks for styles used only with \r

2015-09-05 v1.2 Encode - Hardsub - fixed an issue with the encode being deleted when not muxing audio; fixed some issues resulting from subtitles not being saved 2015-09-05 v2.9 ShiftCut - shift linking point of adjacent lines [new macros added; automation menu changed] 2015-09-05 v3.41 Script Cleanup - fixed nuking \fs

2015-06-20 v1.2 Join / Split / Snap - improved snapping to adjacent lines

2015-06-18 v1.1 Aladin's Lamp - fix for English text

Note1: Some filenames/script names have changed, and some scripts have been merged, so you'll have to reassign hotkeys. Note2: Some settings have been changed/removed, so you may have to resave your config in some cases (likely Relocator, Hydra) Note3: DependencyControl support has been added. Scripts here will keep current naming; github ones will be DC-compatible. DC use is optional - scripts work without it.

2015-06-01 v3.4 Script Cleanup - remove \s \u; remove selected tags only from transforms; remove all tag blocks except first & last; fixed/improved some things 2015-06-01 v2.5 Blur and Glow - default blur part of config; cleaned up the code a bit 2015-06-01 v5.0 HYDRA - gradient: vertical, horizontal, by character, by line, centered gradient; special - convert \s to selected tags; insert tags at text position; reuse tags 2015-06-01 v4.0 Hyperdimensional Relocator - replicate; fbf retrack; killtranstimes; clip2move; clip to reposition; line2fbf: \t accel+move clip; fscx/y support in clip2fax; more clip options in Teleporter 2015-06-01 v4.5 Colorize - added accel to gradient; reverse gradient; improved set colours across; improved tune colours; fixed a ton of things including handling of transforms 2015-06-01 v1.7 Multi-Line Editor - merged with Re-Split; much-improved capitalisation 2015-06-01 v3.4 MultiCopy - pasteover+ ...because sometimes editors and timers don't communicate 2015-06-01 v3.0 Significance - make style from active line; FadeWorks (renamed from Unimportant) 2015-06-01 v2.5 Masquerade - adjusted 'shift tags' to support non-standard characters 2015-06-01 v3.0 NecrosCopy - split by \N: rewritten, positioning by tags and spaces + fixed some things; fax: fscx/fscy support; other things fixed/improved (renamed from CopyFax This) 2015-06-01 v3.0 Change Case - improved; added options; unicode support 2015-06-01 v1.7 iBus - aka "Italy Bold Under Strike" - merged Italics and Bold scripts and added Underline and Strikeout (not too useful, but it was easy) 2015-06-01 v2.3 Line Breaker - improved options for manual breaking 2015-06-01 v1.1 Join / Split / Snap - merged all three for convenience; changed how Join works 2015-06-01 v2.0 Jump to Next - 13 macros; Text/Style/Actor/Effect/Layer/CommentedLine x Next/Previous = 12 hotkeys + 1 for GUI version with all 12 options 2015-06-01 v1.1 Encode - Hardsub - added option to mux the encode with audio

2015-05-08 v4.3 HYDRA - shift transform times for each transform by line or by tag block

2015-05-06 v4.2 HYDRA - increase added value for each line; add before each character; add before each word 2015-05-06 v3.32 Script Cleanup - various minor fixes/additions 2015-05-06 v3.4 Hyperdimensional Relocator - added: switch progression of pos X and Y for fbf lines (right<->down, up<->left, etc.) 2015-05-06 v2.9 Unimportant - added: adjust kara tags for split lines

2015-05-05 v3.0 Recalculator - Regradient (recalculates gradients for existing tags) 2015-05-05 v3.91 Apply fade - fixed some bugs with colours; rewrote some code, so there might be new bugs

2015-04-24 v2.4 Recalculator - added 'repeat last' function 2015-04-24 v1.8 Cycles - changed handling of transforms so that they don't move to end of tag block 2015-04-24 v4.4 Colorize - option to affect only tags outside/inside transforms for 'Tune colours' and RGB/HSL 2015-04-24 v2.8 ShiftCut - added shifting of selection to current frame by END time of active line 2015-04-24 duplikill was modified to handle transforms more correctly. 7 affected scripts: 2015-04-24 v3.3 Script Cleanup 2015-04-24 v3.3 Hyperdimensional Relocator + improved line2fbf + fixed a bunch of minor things 2015-04-24 v3.3 MultiCopy + added 'Repeat last' function 2015-04-24 v2.9 Copyfax This + added '3D shadow' 2015-04-24 v2.4 Masquerade + removed 'mocha scale' + added 'motion blur' and 'merge tags' 2015-04-24 v4.1 HYDRA + better handling of transforms in general 2015-04-24 v2.8 Unimportant + duplicate and shift lines + repeat last

2015-04-20 v2.3 Recalculator - added option to affect only tags outside/inside transforms 2015-04-20 v2.73 Unimportant - fixed some bugs in import/export signs

2015-04-15 v4.05 HYDRA - fixed some stuff regarding tag position 2015-04-15 v3.9 Apply fade - adjusted fade 'from alpha' and 'from colour' to deal with transforms 2015-04-14 v3.2 MultiCopy - support for keeping inline tags with 'Paste text' (read Manual for details)

2015-04-10 v4.04 HYDRA - fixed some minor things regarding transforms

2015-03-26 v3.2 Script Cleanup - improved Clean Tags' handling of transforms

2015-03-25 v3.1 Script Cleanup - switched some things and added info about number of changed lines

2015-03-07 v2.25 Line Breaker - fixed a case where "%" would get nuked from a line

2015-02-15 v3.1 MultiCopy - 'Attach' function can attach data from one field to data from another.

2015-02-05 v1.3 Jump to Next - added 'Jump to Previous' 2015-02-05 v3.03 MultiCopy - fixed "export CR for pad" to nuke tags from signs correctly

2015-02-01 v2.24 Line Breaker - fixed something that I broke before

2015-01-22 v2.71 Unimportant - fixed a bug in import OP/ED

2015-01-18 v2.23 Line Breaker - this time maybe fixed it for real 2015-01-18 v2.22 Line Breaker - fixed a bug where an extra space could be introduced by mistake 2015-01-18 v2.7 Unimportant - option to export chapters in the other format, compatible with mp4

2015-01-10 v2.61 Unimportant - bugfix for 'export sign' with signs.ass not existing 2015-01-10 v2.6 Unimportant - import chapters from xml

2015-01-03 v2.7 ShiftCut - option to not make lines shorter by snapping if given CPS limit is exceeded

2014-12-28 v1.2 Snap - also snaps to adjacent lines

2014-12-12 v3.0 Script Cleanup - added "inverse" option for "kill tags" - kills everything except selected

2014-12-08 v1.6 Multi-line Editor - added capitalization; Add tags has \an8, \i1, \b1, \q2 2014-12-08 v1.0 Join / Split - merged the 2 scripts together; some minor updates and fixes 2014-12-08 v4.3 Colorize - gradient after each linebreak; preserve comments for 'colorize by letter' 2014-12-08 v3.02 MultiCopy - fixed pasting beyond last line of the script; updated clipboard

2014-12-05 v4.0 HYDRA - save proper config; apply tags to sections of text; 'all tag blocks' + 'relative transforms' works 2014-12-05 v2.7 Time signs from timecodes - save config; options to auto remove {TS} comments + auto add blur 2014-12-05 v1.7 Cycles - all 4 cycle scripts in one (4 macros). just delete the old 4 and reassign hotkeys.

2014-12-01 v1.7 Blur Cycle - fixed cases when the current value wasn't in the sequence 2014-12-01 v1.7 Border Cycle - fixed cases when the current value wasn't in the sequence 2014-12-01 v1.7 Shadow Cycle - fixed cases when the current value wasn't in the sequence 2014-12-01 v1.7 Alpha Cycle - fixed cases when the current value wasn't in the sequence; made version consistent with the other 3

2014-11-24 v1.5 Multi-line Editor - added a mode that lets you edit style/actor/effect the same way as text

2014-11-17 v2.61 ShiftCut - fixed info for kf snapping 2014-11-17 v2.41 Blur and Glow - fix something with x/yshad (i don't remember) 2014-11-17 v1.21 Jump to Next - fixed a bug when last line is reached 2014-11-17 v1.01 Encode - Hardsub - delete ffindex

2014-11-14 v2.8 Copyfax This - copy stuff - copy tags after linebreaks 2014-11-14 v3.92 HYDRA - don't allow 2 clips of the same type; fixed 'sort tags' for inline tags or something (idr)

2014-11-07 v2.2 Recalculator - Mirror function for reversing the direction of mocha/fbf data 2014-11-07 v2.91 Script Cleanup - fixed a bug in deleting styles 2014-11-07 v3.01 MultiCopy - fixed Export CR to pad

2014-11-05 v3.8 Apply fade - got rid of some redundant tags; cleaned up the code a bit

2014-10-30 v2.5 Unimportant - motion blur 2014-10-30 v3.0 MultiCopy - rewritten half of the script, fixed some bugs, added new functions 2014-10-30 v1.2 Jump to Next - searches only after the last selected line with inconsecutive selections

2014-10-26 v2.9 Script Cleanup - added remove \q as requested (I really hoped no one would ever need this)

2014-10-22 v1.1 Multiplexer - added more options; fixed some bugs

2014-10-20 v3.21 Hyperdimensional Relocator - fixed some GUI-related bugs

2014-10-17 v2.8 Script Cleanup - clean up transforms; remove \h; apply nuking to start/inline tags; a few other things... 2014-10-17 v3.2 Hyperdimensional Relocator - new functions: "move to" and "randomove" 2014-10-17 v1.4 Multi-line Editor - improved replacer (whole word, regexp) & GUI size estimation

2014-10-15 v1.1 Jump to Next - works for actor/effect too

2014-10-08 v3.12 Hyperdimensional Relocator - bugfixes and minor improvements (vect2rect, randomize, fulltranstimes, negativerot) 2014-10-07 v1.0 Runemap - Something fun to play with

2014-09-29 v4.2 Colorize - Fixed a bug in RGB/HSL + a bug in Invert Colours; changed Brightness to Lightness 2014-09-29 v2.4 Unimportant - What is the Matrix?

2014-09-28 v1.0 Multiplexer - muxes loaded script + video, chapters, alternative script, creates CRC + xdelta 2014-09-28 v1.0 Encode - Hardsub - merged both beta scripts together, so a bunch of encoding + hardsubbing options in one

2014-09-26 v2.4 Blur and Glow - option to have shadow on top/middle layer

2014-09-24 v2.3 Blur and Glow - changed \shad0 to \4a&HFF&

2014-09-20 v1.0 beta Encode - encodes for mocha. if torque's script fails to encode, this might help. 2014-09-20 v1.0 beta Hardsub - script for hardsubbing. some testing would be welcome.

2014-09-03 v2.8 Selectricks - preset: select a specified range of lines, like "1530-2460"

2014-08-25 v2.31 Masquerade - add \shad0 to masks 2014-08-25 v2.2 Line Breaker - separate config file

2014-08-16 v4.1 Colorize - support for linebreaks in gradients; hopefully fixed Shift Colours 2014-08-16 v2.61 Time signs from timecodes - fixed a bug that was fucking everything up

2014-08-13 v4.0 Colorize - Tune colours 2014-08-13 v3.9 HYDRA - add transforms to all tag blocks; fixed some bugs

2014-08-10 v3.11 Hyperdimensional Relocator - fix a bug with transforms 2014-08-10 v2.71 Copyfax This - copy stuff - fix a bug with transforms

2014-08-09 v2.71 Selectricks - enable editor for (most) presets; update editor 2014-08-09 v2.6 Time signs from timecodes - support for timecodes over an hour 2014-08-09 v3.1 Hyperdimensional Relocator - scale clips [\clip(X,m ...]; rounding precision 2014-08-09 v3.92 Colorize - some minor fixes + code cleanup

2014-08-08 v2.3 Masquerade - remask - change an existing mask for another shape without changing tags 2014-08-08 v1.1 Re-Split - support for lines with tags 2014-08-08 v2.3 Unimportant - time by frames; option to shift even rows in Clone Clip 2014-08-08 v2.7 Copyfax This - unicode support (fax gradient); a bunch of fixes and minor improvements in copy functions 2014-08-08 v3.0 Hyperdimensional Relocator - space out letters; clip to frz bugfix, remove expand, clean up code + some other fixes

2014-08-03 v2.2 Unimportant - proper config saving 2014-08-03 v2.2 Blur and Glow - save config; cleaned up some code 2014-08-03 v3.81 HYDRA - fixed a bug with the GUI in mode 1 and 2 2014-08-03 v2.21 Masquerade - shift tags: fixed a bug with \t\clip 2014-08-03 v2.31 MultiCopy - progress messages; cleaned up some code 2014-08-03 v1.33 Multi-line Editor - progress messages; option to expand the GUI when needed; some minor fixes 2014-08-03 v2.1 Unimportant - reverse transforms; randomized transforms 2014-08-03 v3.9 Colorize - randomize RGB/HSB

2014-07-31 v2.6 ShiftCut - rebuilt GUI; save config + custom presets 2014-07-31 v2.2 Masquerade - option to save custom masks and add them to the menu

2014-07-30 v3.8 HYDRA - relative transforms (you have \frz30, set tf 60, get \t(\frz90) - keeps layers with different values in sync.)

2014-07-29 v2.3 MultiCopy - 'gbc text' paste over replicates colour gradients in full; unicode support for 'gbc text' and 'text mod.'

2014-07-28 v2.9 Hyperdimensional Relocator - create a shadow layer instead of \shad; positron code cleanup; save config

2014-07-27 v3.81 Colorize - fixed a bug in Match Colours 2014-07-27 v3.7 Apply fade - support for \4c, \1a, \3a, \4a; unicode support; cleaned up old, retarded code; save config

2014-07-26 v3.8 Colorize - several modes of gradients; option to remember last settings for checkboxes/dropdowns; save config

2014-07-25 v2.0 Unimportant - split into letters; explode; optional config file

2014-07-24 v3.7 Colorize - true HSB adjustment; unicode support for inline things

2014-07-23 v3.7 HYDRA - unicode support for inline tags; optional config file

2014-07-22 v2.81 Hyperdimensional Relocator - make the unicode support actually work 2014-07-22 v2.8 Hyperdimensional Relocator - clip2fax can do fax gradient and supports unicode; clip2frz can average from 2 lines

2014-07-21 v2.75 Hyperdimensional Relocator - fix TP without \pos for margins; fix clip conversion for iclip; remove joinfbflines limit 2014-07-21 v2.2 MultiCopy - added option to paste clips as iclips 2014-07-21 v1.9 Unimportant - added 'clone clip'

2014-07-20 v1.8 Unimportant - dissolve text + logging for replacers 2014-07-20 v2.1 MultiCopy - some more useless things to copy 2014-07-19 v2.11 Recalculator - fixed some bugs with anchoring clips (hopefully) 2014-07-19 v3.6 Apply fade - fade to/from current frame now works with alpha/colours/blur 2014-07-19 v3.52 Apply fade - fix a bug where the alpha part wouldn't work with no tags present 2014-07-19 v2.1 Recalculator - added alternative 2nd value for "Y" things (fscY, Ybord, faY, Y coordinates...)

2014-07-15 v2.71 Script Cleanup - option to apply 'kill tags' only to starting or inline tags (type 'start' or 'inline' in effect) 2014-07-15 v3.61 Colorize - fix a bug in merging with inline tags 2014-07-15 v2.11 Line Breaker - make GUI open when line's effect is "n" + some minor adjustments 2014-07-15 v3.51 Apply fade - fix a bug in merging letter-by-letter fades with other tags 2014-07-15 v2.13 Blur and Glow - remove {*} left after gbc 2014-07-15 v2.51 Time signs from timecodes - a bug fix that I don't really remember

2014-07-14 v2.72 Hyperdimensional Relocator - fixed parentheses in 'transform clip'; GUI remembers main dropdown settings 2014-07-14 v2.7 Selectricks - move selection up or down 2014-07-14 v2.11 Masquerade - alfa timer splits by spaces if no line breaks or @

2014-07-12 v3.61 HYDRA - fix 'tag at asterisk' messing up lines with different text; preset "custom pattern" now handles patterns

2014-07-11 v1.7 Unimportant - reverse text, reverse words, make chapter marks (read Help for more)

2014-07-10 v2.7 Hyperdimensional Relocator - Morph: Adjust Drawing (drawing2clip -> adjust clip -> clip2drawing) 2014-07-10 v2.6 Copyfax This - copy stuff: option to copy tags in line 2014-07-10 v2.62 Hyperdimensional Relocator - Teleporter: fix bug that added \pos for lines with \move; handle \an right w/o \pos 2014-07-10 v3.6 HYDRA - handle 'tag at asterisk' with inline tags; remember input data when reloading GUI 2014-07-10 v2.7 Script Cleanup - added 'remove inline tags' 2014-07-10 v2.1 Masquerade - shift tags: option to remove selected tags

2014-07-08 v2.6 Selectricks - select lines with same text as those in current selection

2014-07-06 v2.6 Hyperdimensional Relocator - added 'clip to frz' (works similarly to 'clip to fax') + some fixes 2014-07-06 v3.6 Colorize - integrated "match colours" GUI into the main GUI

2014-07-04 v1.6 Unimportant - improved importing, numbering, and updating lyrics; added new functions

2014-06-30 v2.64 Script Cleanup - fixed deleting for tags without values, added "Delete unused styles (leave Default)" 2014-06-30 v2.0 Recalculator - ...and so I ended up adding a lot of other things 2014-06-30 v1.82 Recalculator - more fixes because I'm dumb 2014-06-30 v1.81 Recalculator - fixed calculations for Multiply 2014-06-30 v1.8 Recalculator - added option to stack calculations by line

2014-06-26 v1.3 Join - join all selected (keep first) if text (without tags/comments) of all selected lines is the same 2014-06-26 v2.5 Selectricks - added more options

2014-06-24 v2.01 MultiCopy - fixed paste over discrepancies with linebreaks and lines with no visible text

2014-06-21 v1.61 Blur Cycle - bugfix for lines with only transforms 2014-06-21 v1.61 Border Cycle - bugfix for lines with only transforms 2014-06-21 v1.61 Shadow Cycle - bugfix for lines with only transforms 2014-06-21 v1.52 Alpha Cycle - bugfix for lines with only transforms 2014-06-21 v3.58 HYDRA - bugfix (double backslash when line has only transforms) 2014-06-21 v2.53 Hyperdimensional Relocator - bugfix in line2fbf 2014-06-21 v2.51 Copyfax This - option (default) to automatically gradient fax for "fax from clip" + bugfixes 2014-06-21 v2.4 Copyfax This - "fax from clip" can do another fax value at the end of line (for gbc).

2014-06-19 v2.5 Time signs from timecodes - Improved handling of multiple timecodes.

2014-06-16 v2.3 Copyfax This - Copy Stuff: option to hide/unhide checked tags (by making them comments).

2014-06-11 v3.5 Apply fade - added fade in using \ko. supports unicode and inline tags. 2014-06-11 v2.0 Masquerade - replaced the \ko part with alpha shift 2014-06-11 v2.62 Script Cleanup - a little fix to nuke \k tags with decimals (\k3.5) 2014-06-11 v1.57 Unimportant - a little fix for "transform \k to \t\alpha"

2014-06-07 v2.0 MultiCopy - added support for any tag. (use Help button for details) 2014-06-07 v3.5 Colorize - RGB/brightness values are now -255 to 255 (but it's not "real" brightness) Manuals for All Reanimated Scripts

This page contains detailed descriptions for all my scripts. Basic descriptions are included at the top of each script, or within special "help" or similar functions. The ones here are meant to be more detailed, in case the basic ones aren't clear enough for you.

Some global rules for all scripts:

The Esc key almost(?) always works like the 'cancel' function. The Enter key in most cases works as whatever the default button is. (If Enter makes line breaks inside a textbox, you can use Ctrl+Enter instead.)

Some possibly unclear terms that I use, just in case: tag block - a set of tags between curly brackets, e.g. {\blur0.6\bord2\pos(640,300)} start tags / initial tags - the tag block at the beginning of a line inline tags - tags that aren't in the first tag block but somewhere within the text of the line: like {\i1}these{\i0} italics fbf - "frame by frame", referring mostly to mocha-tracked signs, or any signs that have a new line for each new frame of video mocha - software used for tracking signs, in case you don't know gbc - "gradient by character", i.e. text with tags for each character, with values gradually changing (original script written by lyger)

No-GUI Scripts GUI Scripts

Cycles Script Cleanup Multi-Line Editor Significance iBus Blur and Glow MultiCopy Masquerade Line Breaker HYDRA Apply Fade NecrosCopy Join / Split / Snap Hyperdimensional Selectricks Encode - Hardsub Jump to Next Relocator ShiftCut Multiplexer Re-Split Recalculator Time Signs Runemap Aladin's Lamp Colorize Change Case Backup Checker

Cycles Download

(Blur Cycle, Border Cycle, Shadow Cycle, Alpha Cycle)

Purpose: Quick application of blur/border/shadow/alpha

Supports: Detecting and ignoring relevant tags in transforms

These scripts are meant to be hotkeyed (wouldn't really make sense otherwise) and used as the quickest way to apply blur, etc. Each of these 4 scripts contains a sequence. For example blur contains this: sequence={"0.6","0.8","1","1.2","1.5","2","3","4","5","6","8","0.4","0.5"}

You can change the sequence in the script. (Edit the .lua in notepad. Make sure not to screw up the quotation marks and commas.)

The first value is the default. That means if the line has no blur, this value will be applied. Pressing the hotkey again will switch the value for the next one in the sequence. So the first run gives you blur0.6, second gives you blur0.8, and so on. When it reaches the last value in the sequence, it goes to the start again.

If your current blur is not in the sequence, it will give you the first value in the sequence that is higher than the current blur. For example, if your blur is 1.4, you will get 1.5 with the sequence above. However, if it's 0.3, you won't get 0.4 but 0.6 instead, because 0.6 is before 0.4 in the sequence. In other words, the first value, 0.6, is higher than 0.3, so it gets applied, and the process stops there. So if you wanted 0.3 to go to 0.4, then 0.4 would have to be before 0.6 in the sequence.

This doesn't mess with transforms and only applies to the first block of tags.

iBus (Italy Bold Under Strike) Download

Purpose: Quick application of Italics / Bold / Underline / Strikeout

Supports: Handling of all subsequent inline tags

A script for hotkeying that applies italics* to your line. This reads values from style and goes through all italics tags in the line. If the style is in italics and there is no \i tag, you'll get \i0, not \i1. For every italics tag in the line, it switches to the other one (1 vs 0).

Example:

Before: This {\i1}is{\i0} a test After: {\i1}This {\i0}is{\i1} a test

If the style is italics, though, you will get this:

{\i0}This {\i1}is{\i0} a test

Because the style is italics, the first tag becomes \i0. The other tags, however, don't change in this case, because the script corrects a wrong sequence at the beginning, that is two consecutive italics tags with the same value. In other words, since the style here was italics, the first tag became \i0, and thus the second one had to be \i1.

This correction system only checks the first already existing italics tag and doesn't check \r, so not everything gets "corrected".

* This explanation is for Italics, but it works the same for Bold. Underline and Strikeout is probably useless, but it was really trivial to add.

Line Breaker Download

Purpose: Use a hotkey to insert a line break at the estimated most appropriate place in the line

Supports: Properly only English language (Rules for others will be significantly simplified)

This is mainly targeted at editing (i.e. dialogue), but it's useful for typesetters too.

If your line has any line breaks, the script will remove them all. If it doesn't, the script will insert a line break. Where it gets inserted is a result of rather exptensive and complex mechanisms, so I will only explain part of them.

The primary places for line breaks are after punctuation marks, namely . ! ? , : ; ...

Example:

Before: Beyond that sky over there, there are people waiting for you. After: Beyond that sky over there, \Nthere are people waiting for you.

This line breaks naturally after the second "there". If you care about where line breaks are, you obviously want it after that comma, and that's what you get, in this case very easily, by pressing a hotkey.

Now, let's look at another example:

Before: However, one must exercise caution when synchronizing with the other side. After: However, one must exercise caution \Nwhen synchronizing with the other side.

You have a comma there too, but the script sees that the length of the two parts would be extremely disproportionate, so it looks for another solution. In this case, it finds a conjunction that's roughly in the middle of the line and thus is a good place to start the second "line", so the break goes before "when". (The line normally breaks after "when".)

Before: the place you belong is the place where people are waiting for you. After: the place you belong is the place \Nwhere people are waiting for you.

This line normally breaks after "where".

So there's punctuation and conjunctions. After each attempted line break, the script checks the length of the two parts. If the length ratio is too high, it doesn't use that line break and tries something else. If it can't find a grammatically suitable place, the line break goes in the middle of the line. This, however, depends on some settings, so as a last resort, the scripts opens this GUI:

Obviously there's no logical place for a line break with just one word, so it asks you where you want that break. Let's use a meaningful example:

What you do is simply split the line like this (with Enter), and you'll get "You fell into an old well \Nin the woods near your home,".

You can see there's also an All spaces button. This will insert linebreaks at all spaces, i.e. after each word. As this can sometimes be useful for typesetting, there's a trick to bring up this GUI: type "n" in the Effect field, and the GUI will appear.

This script has a number of settings that the user can change to suit his needs better. The setup GUI shows up when you run the script for the first time (that is, a version that has it). To change the settings later, type "setup" in Effect before running the script. You'll see this:

min. characters - Minimum characters required to place a line break. If 50, lines with fewer than 50 characters will be ignored. min. words - Minimum words required to place a line break. linebreak in the middle if all else fails - This can be turned off if you want only grammatical/manual breaks. ^ min. characters - Minimum characters required for line breaks in the middle of a line. force breaks in the middle - Force breaks in the middle of the line rather than after commas etc., if you prefer that. (It's dumb.) disable dialog for making manual breaks - Disables the option to insert manual breaks. allow a break if there are only two words - This overrides 'min. words' and allows a break between just 2 words. Useful for typesetting. enable balance checks - Enables checking the length ratio before/after the linebreak. ^ max. ratio - Maximum ratio allowed. don't break 1-liners - Won't place a line break if the line doesn't break naturally.

These settings can be tuned in various ways. You can for example set it up so that you can just run it on all dialogue, and it will only break lines you want to break. With the default settings, you should only run it for lines you actually want to break. If you want it applied to all dialogue, you should disable breaking for 2 words, probably not break 1-liners, set minimum characters/words to something higher, and possibly disable manual breaking since you might have that popping up for multiple lines.

As for the ratio, 2 may seem like a lot, but this is ratio 2.875 counting by characters and 2.2 counting by pixels:

Looks perfectly fine, in my opinion. Certainly better than breaking the line anywhere else (if you have to break it). It could be a problem with longer lines, though, so when the length crosses a certain limit, the ratio is lowered. (This is not optionable.)

Making this work well is pretty challenging, but I've spent a lot of time testing it and changing it (there were probably 50-100 unreleased versions), and I think it works reasonably well most of the time. Teaching the script complex grammar is pretty much impossible, so there will always be cases where the break has to be changed, which gets us to the other 2 functions of the script.

The Script has 3 macros registered - 'Insert Linebreak' (that's all of the above), 'Shift Linebreak', and 'Shift Linebreak Back'.

Shift Linebreak - Shifts line break by one word to the right. Shift Linebreak Back - Shifts line break by one word to the left.

This allows you to fix breaks that were placed weirdly by using a hotkey. By combining these 3 functions, you can always get the line breaks where you want them pretty quickly.

Join Download Purpose: Join selected lines or a single selected line with the one after it

This is a combination of "Join (concatenate)" and "Join (keep first)". My goal was to make it reasonably estimate which of the two you need, so that there can be one hotkey for both.

If the text (without tags/comments) is the same on all lines, then it's "keep first". If the text is different, it's "concatenate".

If it's more than 2 lines, you get to choose whether you want tags from lines 2+ or not. If you decide to keep tags, it will still nuke ones that should only be in a line once from lines 2+. This means tags with parentheses, \an, and \q will only be kept from the first line.

If start tags in line 1 and 2 don't have transforms, tags that are the same for both are nuked from line 2. This works well enough if only 2 lines are selected, but with a larger selection, as start tags become inline tags, it gets too chaotic to sort out (without making the code several times longer), so you'll have a bunch of redundant tags or maybe some missing. Then again, if you're joining 10 lines with different text and tags, you probably don't know what you're doing anyway.

The script also nukes a {join} comment at the end of line 1, "- " at the start of line 2, all linebreaks, and ellipses if they're at the end of line 1 and start of line 2 (which would otherwise make "...... ").

Note: This description is valid for the version that comes with Split and Snap. The older versions (without Snap) worked differently.

Split

Purpose: Split a line at the estimated most appropriate place

This is mainly for timers, as it splits both text and timing. It's similar to Aegisub's "Split at cursor (estimate times)", but it doesn't use cursor.

Line is split at (in priority order):

1. line break (first one if there are more) 2. {split} or {SPLIT} marker (those can be thus placed where needed by the editor) 3. "- " (dash+space) This is for shitty subs with 2 speakers in one line "Hello. - Hi." 4. period 5. ! or ? 6. comma

If none of the above is found, nothing happens. In such a case, you use Line Breaker to place a line break first, and then you can split at the break. This is supposed to be hotkeyed, and if you learn to use it right, it makes splitting lines during timing very fast. For example if you have 2 sentences in 1 line, you just press the hotkey, it splits the line between the sentences, and you use ctrl+mouse to shift the end of line1 and start of line2 on audio if needed.

An extra function is a sort of auto-correction of crunchyroll stupidity. For some reason, they like to place extremely awkward words at the end of a line, like conjunctions and prepositions. So if a line ends with one of these words, they get automatically moved to the start of the next line: that, and, but, so, to, when, with, the. (not when there's a comma after, mind you)

One more addition is that if the line has only one word (or rather, no space), it gets split into two lines with that same text. If you're timing text where somebody calls someone's name repeatedly, and there's more instances in the audio than in the subtitles, you can duplicate the line this way.

Of course the script gets rid of leading/trailing spaces, and it nukes the {split} tag too.

As for the timing part, there's a mechanism for estimating the times, which I won't explain because it's slightly complicated. It works a bit better than the one inbuilt in Aegisub, but there's really no way to always estimate correctly where the line should be split timing-wise.

Snap

Purpose: Quickly snap a line to nearby keyframes using a hotkey

This is a very simple script, the point of which is to have a hotkey for TPP's snapping to keyframes. At the top of the script you have these settings: kfsb=6 -- starts before kfeb=10 -- ends before kfsa=8 -- starts after kfea=15 -- ends after

This should be self-explanatory. It's just like TPP's settings (when it was in frames, not milliseconds), or like ShiftCut's settings. If you type "gui" in the first selected line's effect field, you'll get a GUI where you can temporarily change the settings.

You can select a number of lines, press the hotkey for this, and any line that's within the distances determined by the settings from a keyframe will get snapped to it. If the script doesn't find suitable keyframes to snap to, it checks if it can snap to adjacent/overlapping lines, with the same settings.

Jump to Next Download

Purpose: Go to the line that comes after all the lines of your current multi-line "sign" (mocha, gradient, etc.)

This is easier to show than explain:

Here you have some mocha-tracked signs. Each frame has 4 lines, so you have 4 different texts. For this example, I'm showing only a small part, but let's say the sign has 200 lines, and you're at the beginning. What you want is to go to the next sign, in this case the one that says "Total: points". (Which it shouldn't really say, but fortunately somebody nuked that colon in time.) So what you do is you select all the lines belonging to the current sign, i.e. all 4 different lines, and press the hotkey for the script. What happens is that the script goes forward line by line and only stops when the text (without tags) of the line is different from all lines in your selection. So you end up with the line with 'Total points' selected. This is very helpful with heavy typesetting when you need to find the next sign and scrolling in thousands of lines is too chaotic.

This is the "Text" mode. You can also go by Style, Actor, Effect, Layer, and Commented Line. Each of those can have a separate hotkey, as well as one for Jump to Previous for each, so use whatever you prefer.

As an extra option, there's also a GUI version with all the other options in one place, if you prefer that.

Aladin's Lamp Download

Purpose: Solve some problems with right-to-left text when typesetting in Arabic

The script has 3 macros. (All can be hotkeyed.)

Fix inline tags When you set an inline tag, Aegisub switches the text that's left and right of the tag. With each new tag, the text around it gets switched. This script switches the parts back into thir correct places. {tags1}text1{tags2}text2{tags3}text3 will be changed to {tags3}text3{tags2}text2{tags1}text1, but the start tags will be kept on the left, and all the required tags for the first part (on the right) will be filled in, including from style. You should be able to input any number of inline tags, run the script, and get back the correct sentence order. You must set all tags first and then run the script, not after each tag. Running the script twice should revert the line to the original state. So you're switching between two states - LTR and RTL. If you want to add another tag to an already fixed line, run the script (this will revert the order), add the new tag(s), and run the script again. You my see it as "working" order and "display" order. When you want to add more inline tags, use the script to switch to "working" order, add the tags, and switch back to "display" order.

Fix line breaks Without inline tags, line breaks work fine, but when there are inline tags, line breaks mess up the lines just as much as the tags. This switches the top and bottom parts of the line and sorts out the tags. This doesn't use styles, so if some tags like colours are only defined for the part after line break, you should set them for the first part as well (though you can do it just as easily after). It may or may not work correctly with transforms. The fact that placing a line break in a line with already existing inline tags switches the text around the \N, between the closest tags, is another issue. ({tags1}text1{tags2}text2a text2b{tags3}text3 - putting a line break between text2a and text2b switches those parts) This I cannot fix because it's way too complex and it would require understanding the language. So the script only switches the top and bottom parts, whatever they are. If you get text2a and b switched, you have to fix that yourself. (I wrote code to switch them, but it only made sense when they were about the same length. In most cases it only made things worse.) It also works only for one line break. If you need more, you should probably just split the line.

Fix punctuation Since Aegisub puts punctuation on the wrong end of the line, this tries to fix it. It moves a punctuation mark from the right end of the line to the left, or if there's a line break, after the linebreak. This way the punctuation -should- always end up at the end of the sentence.

Fix English text If you put English text in the middle of Arabic text, the order of the parts before and after it gets switched. This should, again, switch the parts back correctly. This detects only ordinary Latin characters and won't work with things like "ä".

All of this will need some testing, which can't be done by me because I can't into Arabic, so report your problems to me.

Note: Regarding issues with editing text, it seems that while Aegisub's Edit Box doesn't have RTL support, the lua interface has some, so using the Multi-line Editor should be helpful.

Script Cleanup Download

Purpose: Clean up your script by removing all kinds of unwanted and redundant things from lines (or whole lines)

(Almost) Everything works for selected lines.

There are two parts. The left one does more varied things; the right one just removes specified tags.

Left part:

Remove TS timecodes - removes comments starting with "TS", like {TS 5:36} Remove comments from lines - removes all comments (Comments button is a shortcut for that) Clear Actor field - obvious Clear Effect field - obvious Raise dialogue layer by 5 - raises a dialogue line's layer by 5 if it's 4 or lower (Dial 5 button is a shortcut) Clean up tags - joins neighbouring tags - {\tag1}{\tag2}; removes anything detected as redundant (duplicate tags in the same block, tags at the end of a line, \frx0\fry0 in start tags, multiple \fad tags, \fad with 0 times, {\\k0}, \r at the start of a line); rounds values of tags to 2 decimals (Clean Tags button is a shortcut) Delete commented lines - deletes lines that are commented out (not visible on screen) Delete empty lines - deletes lines with no text Try to fix alpha / colour tags - fixes some malformed tags like alpha00 Fix start/end/double spaces - removes leading, trailing, and double spaces Print info - tells you how many lines were changed in the Text field ALL OF THE ABOVE - Applies all the functions above, checked or not

Remove all colour tags - obvious Remove all alpha tags - obvious Remove all rotations - obvious Remove all perspective - removes all rotations, fax, fay, and org Remove size/scaling - removes fscx, fscy, and fs tags Remove linebreaks - \N - removes linebreaks, leaving one space between the words before and after the \N Remove linebreaks - \N (nospace) - raw linebreak removal - simply deletes "\N" Remove hard spaces - \h - obvious Delete unused styles - deletes styles not used on any lines (this always applies to the whole script) Delete unused styles (leave Default) - same but doesn't delete styles containing "Defa" or "Alt" Move transforms to end of tag block - places transforms at the end of the tag block Remove inline tags - obvious Remove inline tags except the last - deletes all blocks of tags except the first and last Remove all {\tags} from selected lines - removes all tags (Tags button is a shortcut)

Run selected will apply anything checked on the left side.

Right part:

Kill checked tags button will remove any tags you check in the list above it. The one distinction is that you can choose start and/or inline tags. Just in case you don't know what that means:

{\blur0.6}How does {\i1}this{\i0} work?

The blur is a 'start' tag; the italics are 'inline' tags. The checked tags will only be removed from the checked sections. By default, both are checked, so all tags of the checked kind get removed. In this case here, checking 'blur' but not checking 'start' (only 'inline') would not do anything. inverse - remove all except checked tags from \t - remove only from within transforms

If you press Cancer, you might get cancer, so don't ever do that unless you want cancer.

Blur and Glow Download

Purpose: Apply blur correctly to signs with border / create a 'glow' effect

Features: double border with its own properties; adjustment for fades; add only new layer for signs that already have layers change layer; repeat with last settings; save configuration

Supports: Getting info from styles, handling of transforms, alphas, fades, \r

What is 'correct' blur? Left is wrong. Right is right. No pun intended.

"Blur / Layers" creates layers with blur, and usually doesn't require messing with the other options.

If you want double border, you have a number of options.

If the regular border is 2, the border on the bottom layer will normally be twice that, so 4. You can check 2nd b. size and set the size of the 2nd border. You set what you want it to look like counting from the first border. In other words, the default would be 2. If you set it to 4, the outer border will be twice as thick as the first one, and the tag will be \bord6.

You can also check 2nd b. colour, which in the case above was changed to light blue / cyan.

You should check double border if you want the default double border. If you have auto double checked, then checking 2nd b. size or 2nd b. colour automatically turns the double border mode on, so you don't have to check double border. bottom blur is for whichever layer is the bottom and not glow, so regular border with 1 border, and 2nd border with double.

"Blur + Glow" creates glow. This is what we call glow:

Left is no glow, middle is glow with default settings, right is with glow blur 8 and glow alpha 30. glow blur is the size of the glow (value of \blur). glow alpha is transparency - 00 is most visible, F0 would be very faint. If 00 is not as strong as you need it to be (usually with larger blur values), you can duplicate the glow layer.

Here's an example of a real sign with glow: It's mostly useful for signs with no border but can be used with border too, but then you have to be careful to not have too much blur compared to the border size.

Normally shadow from the original sign gets applied to bottom layer with border and to glow layer. keep shadow on top layer leaves shadow on the top layer, like this:

keep shadow on middle layer does the same on middle layer if you have double border. If you check both, you'll have a shadow on both. The bottom layer will still have a shadow in any case, so you have to nuke it later if you don't want it. That's easy to do with Script Cleaner, but if there are rotations involved, you'll have to use \4a&HFF& instead of nuking the shadow. fix \1a for layers with border and fade is for use with fades.

Left is when you don't use it. Right is when you do. You can see the difference in primary colour. What it does is that it applies \1a&HFF& during the fade and only transforms into colour when the fade ends. transition is the time it takes to transform from that \1a&HFF& to full colour. 0 is instantaneous, i.e. alpha will be there for the whole fade, and the first frame after fade will have full colour. The higher the number, the longer the transition. I use the default 99% of the time, but some cases may require tweaking.

Fix fades button either adds those alpha transforms if they're missing, or recalculates them based on \fad. Recalculating is useful when you shift a sign like episode title into another episode, but while the fades are the same, the length of the sign is different, so you need different timecodes for the ending transform. only add glow will only add glow to layer with border, instead of creating a layer for the border too. only add 2nd border will only add second border, instead of creating a layer for the first border too.

If you add glow or borders when you already have a layered sign, you'll have to shift layers for the top lines. For this you can use Change layer. repeat with last settings will use whatever settings you used last time (unless you rescanned automation directory or restarted Aegisub). save configuration saves the current configuration of your GUI as the default settings.

If there's no blur, default blur is used. It's set to 0.6, but you can change it in the GUI.

The script supports xbord, ybord, xshad, and yshad, and has basic support for transforms and \r.

HYDRA Download Purpose: Easily add tags to multiple lines

Features: transforms with several modes; inline tags with several presets; gradients; a number of special functions; modification of tags line by line; options for what lines to apply things to; 3 GUI modes; repeat with last settings; save config

Supports: Non-standard characters in Gradient

Contents Basic Functionality Transformable Tags Transforms Tag Position Add With Each Line Gradient Special Functions General Settings

This is like the old "add tags" script, except 1. you don't have to type the tags, 2. you don't get duplicate tags, 3. you can easily do transforms, 4. you can apply the tags to specific layers, styles, etc., 5. you can make inline tags in several different ways, 6. you can make gradients, and 7. you have a bunch of extra useful functions. In other words, anyone still using "add tags" should be ashamed.

The basic functionality is extremely simple. Let's say you want \bord3 and \shad2.

You check the checkboxes, type the values, and click Apply.

If you still find the GUI confusing, you can set the Start mode at the top right to "1". This pretty much limits it to adding tags, without anything extra or confusing. It also loads instantly while the full GUI loads in anywhere between 1 and 3 seconds for me. There's also the Medium mode:

This adds the other alphas, \an, additional tags, and already lets you adding values by line. We'll get to that in a bit.

Additional tags lets you type some extra tags, should there be a need to add something non-standard, or \fn.

When you load the full GUI, you can do transforms, which is just as easy as adding regular tags. If you want to transform to \bord3 and \shad2, like in the previous example, you do the same except you click on Transform. That will give you \t(\bord3\shad2). If you want times, you set them in the Transform t1, t2 fields. Accel is there as well.

That's pretty much all you need without going into specialized functions, so it's really easy.

While the basic functions are powerful enough and pretty much a must for anyone who wants to typeset without being slow as hell, HYDRA has much more to offer to advanced typesetters used to dealing with dozens of lines at a time and huge, chaotic scripts.

First let me explain some additional notes to the basic tags.

There are two ways here to do \[number]a tags, or alphas for colours. One is on the right where it's pretty obvious. The other is using the colour pickers. These have both the colour and its alpha value, and you can apply either the colour, or the alpha, or both. By default it uses only the colour. If you want the alpha too, you check Include alphas. If you want only the alpha and not the colour (so an alternative to those dropdown alphas that only have a limited set of values), you check the only checkbox next to it. Note: on older versions of Aegisub this doesn't work. (Minimum Aegisub r7993 required.)

Italics, Bold, Underline and Strikeout work only for start tags (not inline tags). They're being kept separate from the "transformable" tags for reasons that will become clear shortly.

Global fade lets you apply the fade not to each line, but to the selection as a whole, so that the first selected line will have the fade in, and the last selected line will have the fade out. (You can do much more with fades using Apply fade.) There's a layer checkbox, which obviously isn't a tag, so that changes the layer of selected lines. It changes by the given amount, so the layer proportions of your lines will be kept. (Layers 2, 3, 5 with "+2" will become 4, 5, 7.) If you try to go below 0, the layer won't change, rather than go to 0.

Transformable Tags

Let me explain something here before we get into transfomrs, as this will be relevant for a number of functions. Transformable tags are tags you can put into a transform, In other words, tags that can change value in one line. At the same time, these are also gradientable tags, except clip, but HYDRA doesn't do clips anyway. They are those you can see in Start mode 1, except Italics & Co., fade, and q2. Specifically, they are: colours, alphas, bord + shad including x/y versions, fs, fsp, fscx, fscy, blur, be, fax, fay, frx, fry, frz. Clip is the last one, but it doesn't apply here, so we'll exclude it for HYDRA.

This category of tags has some special uses in HYDRA, which is why I'm explaining this. Now, on to transforms...

Transforms

For simple ones, you don't have to do anything other than select the tags and press the correct button. t1, t2, Accel - These are the regular \t times and acceleration.

Count times from end - This will count transform times from the end of the line, so values 500,200 will mean that the transform will start 500ms before the end of the line and end 200ms before the end of the line.

Transform mode - normal - This is a regular transform. Transform mode - add2first - This will not create a new transform, but instead will add the tags to the first existing transform in the line, assuming there is one. If there isn't, nothing happens. Times don't apply here. Transform mode - add2all - This will add the tags to all transforms found in the line. Times don't apply. Transform mode - all tag blocks - This will add the transform to each tag block, i.e. this: {\tags\here} This can be useful with gradient by character, for example if you have gradiented colour and want to transform all letters to black. Even more fun, you can have frz gradiented by character and apply a relative transform!

Relative transform changes the mechanism so that tags don't transform TO the given value, but BY the given value. This means that when given frz30, you won't get \t(\frz30) unless your curernt frz is 0, but whatever is needed to rotate each line BY 30 degrees. The point of this is that when you have several lines with different rotation values, you can rotate them in sync this way. Or for example you may have a sign with two borders and you want both borders to grow by the same amount. (Default settings would transform both to the same value, thus basically making one of the lines invisible.) So if you have \bord3 and \bord5 and transform by 2, you'll have the first line transform to 5 and the second to 7.

Shift times/interval has several functions. With 'normal' transforms, it shifts \t times by this amount each line. (0,300 + shift by "200" -> 0,300 - 200,500 - 400,700...) With 'all tag blocks', it shifts \t times by this amount each tag block, transforming the blocks (letters?) in sequence. With Back and Forth Transform, it's the interval (explained later).

You can do a lot of things if you combine all these options, and more if you combine them with some of the options below.

Tag position

This allows you to make inline tags. In that text field, you have the clean text of your first selected line. You place an asterisk there, like you see in the example above, and the tags you select will go there, so in this case before the word "here". It will work for any line that has such text, while lines that don't contain it will remain unchanged. This also works with transforms in normal mode, while the presets don't.

--- presets --- before last char. - This will place the tags before the last character of visible text, no matter what the text is. This is very useful for mass gradienting lines with different text. If you want to style a song so that it's gradiented by character from green to blue, for example, you first set the green at the beginning of the line, and then set blue before the last character this way. Then you run gradient-by-character, and all lines will be gradiented whatever the text is. in the middle - This will count characters and place the tags in the middle of the line. (If odd number, it goes before middle letter.)

1/4 of text - Similarly, this counts the characters and places tags at 1/4 of the line. (If 20 characters, it goes after the first 5.) Obviously this works in the same manner for the other presets with this pattern. custom pattern - This uses an asterisk, just like the basic mode, but it can use shorter patters, as opposed to the whole text of the line. For example you can use the pattern *and, and tags will be placed before the word "and" in any line that contains that word, no matter what the rest of the text is and however many times that word is in the line. Note that this doesn't recognize "words" but only patterns of characters, so you'd also get the tags before "android". It's also case-sensitive, and there can't be tags/comments inside the pattern. section - This lets you put tags before a given pattern and then changes the tags back after it!

So as you may have seen in the screenshot in the Blur and Glow section, here the word "one" needs to be red.

So what you do is select section from the preset menu, and leave only the word "one" in the Tag position text field. Then you select red colour from the tags and apply.

The word "one" becomes red, and the tags after it return back to white. As with the previous option, there can't be any tags/comments inside the section. You can apply it to several words, as long as there are no {} in the pattern in the actual line.

every char. - This places the tag(s) before every character. It's the same tag(s), so it's not that useful on its own, but you can gradient them with Recalculator. You can also for example set \bord2 for each character and then apply those shifted transforms for all blocks with \bord5, and the border will grow letter by letter in a karaoke-ish effect.

every word - Same as previous but with every word.

text position - With this option, you type a number in the Tag position field, and tags will be set at that position. 0 is the start of the line and it counts visible characters (as in not tags/comments) including punctuation and spaces. So if you type 12, the tags will go after first 12 characters in all selected lines, whatever the text. If the line has 12 characters or less, nothing happens to it. To make it more fun, you can enter negative numbers and count from the end, so "-1" will go before last character. But it doesn't stop there. You can shift the position for each line. Let's say you want to start at the beginning and shift by 2 characters for each subsequent line. In that case, you would type 0+3. It can look like this...

...or you can apply it to fbf lines and create a sort of animation.

You can make all kinds of combinations, and you can run this multiple times with different settings, and it will all be merged.

What you have to be careful about is that you don't screw up the pattern. We're counting letters, so only whole numbers apply. The first can be a number or - and number. The second has to be - or + and a number. The first has to be at the beginning of the field. (If anyone cares, it detects "^%-?%d+" and ".([%+%-]%d+)".

Add with each line

This is sort of related to the previous, as it also shifts for each line, but in a different way. It adds value to applicable tags for each line. So if you start with \bord2 and type 3 into this field, line by line, you get 2, 5, 8, 11, etc. This works in transforms as well. It should be obvious which tags it can apply to, but let's list them here: (x/y)bord, (x/y)shad, fs, fsp, blur, be, fscx, fscy, fax, fay, frx, fry, frz These are transformable tags with number values, i.e. excluding colours and alphas. You can use negative values here as well. It applies to all the tags from that list that you check, so of course it doesn't make sense to use it for \fax and \fs at the same time. Again, you can get more effects by combining this with other functions.

Gradient I felt like the GUI had space for another button, so after thinking about what else I could do with HYDRA, I decided on gradients.

There was no reason not to do all three versions - vertical, horizontal, and by character. Vertical Gradient You may ask what's the point in the light of lyger's gradients existing. I had to make sure there was some point in it, so I had to come up with something extra. There is not that much point in the vertical/horizontal ones, and they have a clear disadvantage against lyger's, because as this works the same for each selected line and utilizes only one set of given tags, it can't handle inline tags. Or at least not the way lyger's script does.

There is, however, the Centered gradient, as well as the fact that you can gradient several lines at the same time - as long as the same settings are suitable. You can also use floating point for the Horizontal Gradient clip stripes, so the clips can be for example 2.3px wide.

The winner here, though, is the gradient by character, which can also be centered, can use accel, and doesn't mind other inline tags present. Plus all of this just works a bit differently due to the nature of different GUI, so sometimes lyger's scripts may be more suitable while other times this one may.

Now about how it all works here. The vertical and horizontal ones require a clip, like lyger's. Gradient by Character Rather than gradienting between two lines, the transition here is made between the current state of the line and the given tags. Current state of the line includes style. So what the line looks like is what you're gradienting from, and the tags you select in HYDRA are what you're gradienting to.

On the right, you can see examples of each gradient mode. Aside from the obvious - colours, the vertical one was also given \shad, \blur, and \fscx, the horizontal one got \bord, \shad, and \fscy, and the one by character got \xshad and \frz. All of these are easily made in one run.

As I already mentioned, accel can be used for all three. Shorter rotations is the same thing lyger has. It means that if your \frz is 350 and you check Centered Gradient \frz10, it won't rotate the whole circle but instead go from -10 to 10. It's probably the logical thing to do 99% of the time, so it's on by default.

Centered gradient is one that transforms to the centre and then back. It's pretty common in anime, so there should be some use for it. As you can see on the right, though, and as you may have experienced at some point, due to the nature of fonts, you get too much of the orange and Centered Gradient, accel 3 too little of the purple.

This is where the accel setting is really useful. Here's an example of the same gradient with accel 3. This is probably closer to what you'd want to get. As you may need a few attempts to get it exactly right, note the section on the reuse function further down.

This works with GBC as well, with an additional bouns. Centered GBC, 'hydradi*ent' The same way you use asterisks in the Tag position field for inline tags, you can use it to give a GBC an alternative "centre". So you can get a gradient that's centered anywhere in the line. Of course if you apply it to multiple lines with different texts, it will only work for the one(s) that match the text of the first one. The other ones will be centered normally. Here's an example with the pattern 'hydradi*ent'. The gradient is centered between 'i' and 'e'.

Now what happens with those inline tags when you run a vertical gradient on this? GBC + Vertical If you select black colour, everything will get gradiented to black, whatever the original colour is. You can't gradient each letter to a different colour (like you can with lyger's, which is its main advantage), but the colours do get interpolated separately.

As already implied, when you have a GBC line, you can add gradients for other tags and they will be merged with the existing ones. On the other hand, you're always doing a gradient across the whole line. You can still use some tricks when working with one line, though. GBC modified For example, for the image above with gradiented rotation, let's say you want to gradient border to 5, but only for the 'hydra' part, and leave it at 5 for 'dient'. You would simply copy the 'dient' part from the Edit Box, delete it, apply GBC to \bord5, and paste back what you copied.

Or copy+delete the 'hyd' part, apply gradient to \frx50 to 'radient', paste pack, and you get this: The 'hyd' part didn't change, but the rest has a gradient for \frx.

You can also do a gradient by several characters, if you type a number in the Tag position field. The field has to contain only 1 digit and nothing else. If the number is too large, nothing happens.

In the end I also added Gradient by line, which is a simple fbf transform. It has little advantage over lyger's script, but it was really easy to add once I had the other gradients done. The differences from lyger's fbf-transform are: 1. the way the GUI works 2. only works for start tags 3. doesn't care about the text (though lyger's has that option too now) 4. you can restrict use by layer, etc. (so for multi-layered fbf lines, you don't have to change the selection if you want to fbf 1 layer) 5. Centered gradient works for this as well

There are plenty of things that can be done with this and combined with other functions/scripts. Note: If you forget clip for V/H gradient, add the clip and use the Repeat Last button.

Special functions This is all for the Special button.

fscx -> fscy - Applies the value of fscx to fscy, making them the same. fscy -> fscx - Same but the other way round. move colour tag to first block - If you use hotkeys for colour pickers, the colour gets sometimes applied somewhere in the middle of the line or at the end because that's where the cursor is in the textbox area. This moves a colour tag that isn't at the start of the line to the start. If it finds more, it deletes them all and uses the last one in the line. This is a lot more useful if hotkeyed or at least if it's set to be the default choice, but if you need to set colours to a bunch of lines and don't want to make sure the cursor is always at the beginning of the line, you can just set the colours wherever and then run this. convert clip <-> iclip - Changes clips to iclips and vice versa. clean up tags - This is the same as in Script Cleanup, so scroll up. sort tags in set order - This sorts tags in each tag block based on the order in settings. back and forth transform - This will transform back and forth between the current state of the line and tags you select. So for example, you select \bord 10 and \frz 20 and run the script. It will read the current bord and frz from the line or from style and create transforms based on given interval. Interval is the Shift times/interval field. A value of 500 means that it will take 500ms to transform to \bord10\frz20, then 500ms to transform back, another 500 forward again, etc. for the whole duration of the line. This way you can create wobbling effects and such. select overlaps - This used to be shipped with Aegisub. I don't know if it still is, but somebody wanted that included in HYDRA, so here it is. It selects lines that overlap with other lines. convert clip to drawing - Uses coordinates from a clip to create a drawing. convert drawing to clip - Same but the other way round. convert strikeout to selected - Converts \s1 to the tags you select, and \s0 back to the original state. This allows you to use a quick on/off trigger for multiple tags at the same time. You apply \s to a word or section of text in the Edit Box, and then you can convert it to whatever tags you want. chequerboard clip - This creates a checkerboard clip. Not too useful, but you can convert it with the above tool to drawing. This also allows you to resize it with the scaling tool and convert back, so you can get various sizes. create 3D effect from shadow - This is one of the more useful things in this menu.

It's the difference between these two. Left is ordinary shadow. Right is the 3D effect. You can best see it on the top part of the last letter. The space between the letter and the shadow gets "filled". (Or you can scroll down to Masquerade which has this more easily accessible and a better picture.) split line in 3 parts - Use the fields for 'Transform times' to set duration of line 1 and 3. If you set for example 200 and 300, your line will be triplicated, with the first one being 200ms long, the last one 300ms, and the middle one whatever is left of the original duration. If you set either of the two to 0, you'll only have 2 lines. This can be useful for song styling when you want to apply some transforms to the first or last 500ms, for example, because applying the transforms to the lines with the whole duration can be much more laggy, and lines with too many transforms can look too chaotic to work with them.

Apply to

This is quite simple. You can choose to which of the selected lines you want to apply the changes based on the 4 restrictions. When dealing with multi-layered signs, you may need different tags for different layers, so this can make it easy. This way you don't have to change the selection each time.

It should be noted that when adding a larger value each line, it counts only lines where it's applied, not all selection lines. Same goes for Shift times and the text position preset.

Repeat Last will run the last used function with the last used settings. show / reuse

This is what works for transformable tags, explained above. It doesn't include Additional tags.

If you check show, once you apply the tags, you will be shown a textbox with the tags in the applied form, like "\bord2\shad3\fs50". Not extremely useful, but you can copy and save it for later in case you want to use the same set of tags again - in which case you'd just paste it into Additional tags without having to check boxes and type values again.

The bigger point here is the reuse function. Unlike Repeat Last, this only remembers the tags (the transformable ones), but it lets you use a different function and restrictions. If you simply applied tags before, you can now reuse the tags for other lines, to make transforms or gradients, or to apply to different layers, etc. For example, you may have 5 layers and want to apply things to layers 3 and 4. If you don't remember what tags you used last time and whether they're the ones you'd like to reuse, checking show at the same time will tell you. reuse can be very useful if you do transforms or gradients, you get the tags right, but you mess up the other settings. You can fix the settings and reuse the tags.

Save Config saves the current configuration to a file named "hydra4.conf" in your APPDATA folder.

Help button loads an extra part of the GUI with some basic usage instructions.

Hyperdimensional Relocator Download

Purpose: Do all kinds of things involving mainly \pos, \move, \org, \clip, rotations, and masks

Check SpaceTravel Guide and click on Positron Cannon, and you'll have all the information you need.

I'll just add some things here that use images.

FBF Retrack (Positron Cannon)

As is explained in the Guide, this has two modes - simple, and smoothening.

Simple mode is like fbf transform for \pos, but you can do layered signs in one go, and you can use different accel for X/Y.

That would hardly be interesting enough, though.

If you check smooth, you can smoothen some tracking data. Normally you do it for FBF lines, but here I've set them all to the same frame so that you can see what's happening. (This is possible when you uncheck layers.)

So on the right, you can see two 'tracked' signs. The first one is really messed up in some places; the other has just small 'glitches'.

A smoothening factor is applied in the Force Field. It can range from 0 to 100%, so settings beyond those values get reset to 0 or 100. 0 does nothing, but with a setting of 2, you'd already see some small effects.

In the second image, I used a fairly low value of '20'.

You can see the blue track didn't change much. Only the two lines on the bottom left edge aren't standing out that much anymore, which is pretty much the whole point. If your tracking in Mocha does something weird in one random frame, this may help, though it is yet to be seen how practical this actually is.

On the yellowish track, which was a lot more messy, the differences are more obvious. Not that this would likely happen with Mocha data, but there can be other uses. For example if you use a shaking effect for something, and later decide that it's shaking too much, this can smoothen it without your having to redo the whole thing. This is pretty much the opposite of a 'shake' effect.

So that was 20%. This next one is with '50'.

If the blue one was real tracking data and the few odd frames were really glitches, this actually looks close to what might be a 'fixed' track.

The yellow one is not really a realistic example of anything, but you can see what the setting of '50' does to a track, which is all I'm trying to show here.

I haven't tried it on many examples, so it will require some experiments to find out how useful this can be for different kinds of tracks.

The last image is with the strongest smooth setting - 100. The first half of the blue track is pretty smooth, but the second part, where you have bigger changes of direction, is probably already overdone, as the track is shifted towards the centre of the curve. You can apply this to only part of the track, though.

The yellow track shows the overal effect on random short glitches.

There's actually another setting - reference lines. You can enter a number in the Repositioning Field for that. Anything below 1 is defaulted to 1. With that basic setting, each line is compared with 1 line before and 1 line after. With '2', it's also compared against 2 steps back and forward (where applicable), and so on. Some of the yellow track may look worse with '100' than with '50' because a line may be getting 'corrected' more strongly by referencing two frames that are messed up too.

The higher you go with the reference lines, the less power that reference has, so there's no point going too high. Which of the two settings is more useful will depend on the track.

While the highest setting for smoothening is 100, there's nothing stopping you from running it again, as many times as you want. With enough repetition, you would reach a straight track in the end.

Recalculator Download Purpose: Recalculate values of tags by multiplying or adding

Features: Recalculation of values for all applicable tags, drawings, \t times; supply 2 different values where applicable; modify values line by line; distinguish between regular tags and tags in transforms; Regradient; repeat last

This part is relevant to Multiply/Add:

Multiply Change values to - This will increase/lower values of selected tags based on given percentage. With a value of 120%, you will get a 20% increase in value of checked tags, so fscx100 will become fscx120, fs60 will become fs72, etc. With multiply/add more with each line and fscx100, you'll get 120, 140, 160, 180, etc. for consecutive lines.

Add Increase values by - This will increase/lower values of selected tags based on a given number. With a value of 5, the value of checked tags will increase by 5, so fscx100 will become fscx105, fax0.05 will become fax5.05, etc. With multiply/add more with each line and fscx100, you'll get 105, 110, 115, 120, etc. for consecutive lines.

Alternative 2nd value allows for a different value for all Y things (fscY, Ybord, Yshad, frY, faY, all Y coordinates) + fad2 and t2. It will be used as Multiply or Add depending on the button you press.

Example: Checked fscx, fscy, pos x, pos y; tags are \fscx100\fscy100\pos(300,200)

-> Change values to 150%; Alternative 2nd value 250%. Multiply. -> Result: \fscx150\fscy250\pos(450,500)

-> Increase values by 20; Alternative 2nd value -10. Add. -> Result: \fscx120\fscy90\pos(320,190) anchor clip - This makes sure that multiplying values for clip coordinates won't send your clip somewhere off the screen. Rectangular ones are anchored in the middle, vectorial ones at the first point of the clip. all pos/move/org - This is like checking all 8 pos/move/org checkboxes, so that you don't have to click on all of them.

Rounding - How many decimals are allowed for recalculated values. regular tags / tags in transforms - Results are applied only to tags outside transforms, or only to tags inside transforms, or all if both are checked (default). This function only applies to top 3 rows of tags except kara, and to clips.

Mirror - This is intended for mirroring mocha data. Applied to fbf lines with pos going from 200 to 260, it will go from 200 to 140. Works with position, origin, rotations, and rectangular clip. If clip changes size/shape, results will be weird. Also works with move (though that makes pretty much no sense to use) and fax/fay. (I'm not sure how it's useful, but Hdr wanted it.)

Regradient

This will recalculate gradients that already exist but need to change values. If you change the first or last value of the gradient, this will recalculate the values in between. It can be used for a specific tag in lines with multiple gradient, as it won't touch the other tags (and bypasses transforms). Conditions: 1. you must check the tags you want to regradient, 2. there must be at least 3 instances in the line (outside \t).

Example: {\frz1}This {\frz2}is {\frz3}an {\frz4}example {\frz5}of {\frz6}regradient

Change the first \frz: {\frz16}This {\frz2}is {\frz3}an {\frz4}example {\frz5}of {\frz6}regradient

Check frz, press Regradient: {\frz16}This {\frz14}is {\frz12}an {\frz10}example {\frz8}of {\frz6}regradient

[ ] - This is a workaround for spaces with full-line GBC. (It won't do anything in the example above.) GBC counts values for spaces, but the tags get removed because they're useless. Regradient would count values without them since the tags for spaces aren't there, so this temporarily adds fake tags for the spaces. It's only applied if the number of the tags in question matches the number of characters in text, which should mean it's a full-line GBC. It should thus be safe to have it always enabled, but if something behaves weirdly, you can try disabling it. repeat last - Repeats Multiply/Add/Regradient with last settings.

Colorize Download Purpose: Operations with colours

Features: colorize by letter; match colours; gradient; adjust RGB/HSL; remember last settings; repeat with last settings; save config

Supports: Non-standard characters for all functions; handling of line breaks, transforms, and inline tags; getting info from styles

Contents Colorize by Letter Shift Colours Tune Colours Set Colours across Whole Line Gradient Reverse Gradient Match Colours RGB / HSL General Settings

The GUI covers all kinds of different operations, so let's break it down by buttons.

Colorize

The blue+green area is what settings the button uses. This still covers several varied functions, though.

The default action is Colorize by letter, and the green area covers the relevant options. What you do with this is pick a number of colours (2-5) and set them letter by letter to the whole line. (Spaces don't count.) Colours is where you select how many colours you want. The 5 colour pickers are where you select the colours. Apply to is where you select the colour type you want this applied to. Colorize by word will switch colours after each word rather than character.

Don't join with other tags will not join the first colour tag with the initial tags. (I don't remember why this option is there, and you probably shouldn't use it.) Note: Comments are preserved (v4.21+), but all shifted to the end of the line.

Shift

The Shift function is closely related to this. It simply shifts all the colour tags by one character. (Doesn't work by word.) It was only made for one colour type, so it won't work if you have two types colorized. The type is determined from the tag before the last character, and the tag block must only contain the colour. If Shift base is # of colours, you have to select the number of Colours so that the script knows which colour the first letter should be. This should be used for a line colorized the way described above. If, however, you have for example a gradient and want to shift the colours of the whole line around, you set Shift base to line, and the first letter will be the colour that was previously the last letter. Continuous shift line by line is what really makes this interesting. You use it on fbf lines, and the colours get shifted by another character with each line. You may have a different colour for only one letter, shift by line, and the effect will be that colour running across the text.

Other Colorize functions

Tune colours - Loads all colours from a line into a GUI and lets you change them from there. For more info, check this. You can select all/regular/transf to load colours only from transforms, only those not from transforms, or all. All selected loads unique colours from all selected lines, instead of all colours line by line. While without this checked, you affect each colour separately when there are multiple same ones, this will only load each colour once for eact type (\c, \3c), and replace it in all its instances. So for a selection of lines where you need to change the shade of a particular colour in all lines, you would use this. It's like ctrl+h, but it allows you to use the eyedropper tool to pick the colour.

Set colours across whole line This is like a preparation for gradient-by-character. Select number of colours and colour type. For 3 colours, it will place one at the start, one in the middle, and one before the last character. Works for 2-10 colours and sets them evenly (as much as possible) across the line. (Then you can run GBC.)

Gradient - Creates a gradient by character. There are two modes: RGB and HSL. RGB is the standard, like lyger's GBC; HSL interpolates Hue, Saturation, and Lightness separately. Use the \c, \3c, \4c, \2c checkboxes on the right to choose which colour to gradient. Shortest hue makes sure that hue is interpolated in the shorter direction. Unchecking it will give you a different gradient in 50% cases. Double HSL gradient will make an extra round through Hue. Note that neither of these 2 options applies to RGB. Use asterisks places asterisks like lyger's GBC so that you can ungradient the line with his script. Restart after each \N will create the full gradient for each line if there are linebreaks. You can use acceleration if you type it in Effect in this form: accel0.8 There are several differences from lyger's GBC: - RGB / HSL option - You can choose which types of colour you want to gradient - Other tags don't interfere with the colour gradients - You can use accel - Asterisks are optional

Reverse Gradient

This is a simple thing that reverses the order of colours for each selected type. It only applies to existing colour tags (not to style) and ignores transforms. So if you have colour tags for black-red-green-blue for \3c, and select \3c, they will now be in blue-green-red-black order. This means that a full gradient will get reversed completely.

Match Colours

This should apply to all colour tags in the line. c->3c: outline colour is changed to match primary 3c->c: primary colour is changed to match outline c->4c: shadow colour is changed to match primary 3c->4c: shadow colour is changed to match outline c<->3c: primary and outline are switched Invert: all colours are inverted (red->green, yellow->blue, black->white)

RGB / HSL Violet colour applies to both parts.

Rather obviously, this adjusts Red/Green/Blue or Hue/Saturation/Lightness for all colour tags of selected type in all selected lines. As colour values are hexadecimal, one step is 1/255th of the colour scale, and thus -255/255 are the limit values. Apply to missing means it will be applied to the colour in Style if there's no tag in the line. Randomize - Example: if you set Lightness (or any RGB/HSL) to 20, the resulting colour will have anywhere between -20 and +20 of the original Lightness. Again, all/regular/transf allows you to set restriction for which tags you want to use this - non-transforming, in transforms, or all.

General Settings

Remember last - Remembers last settings. (Until reloading automation directory.)

Repeat last - Whichever button you press, the settings from the last opening of the GUI will be used.

Help - Loads help menu for the section selected from the dropdown menu.

Save config - Saves current settings as defaults.

Multi-line Editor Download Purpose: Edit multiple lines like on a pad

Features: Edit text, style, actor, and effect; replacer with regexp; remove tags/comments/linebreaks; capitalisation

Supports: Capitalisation for non-standard characters (ä, è, ø) (from v1.7)

This was written so that you can edit multiple lines without having to jump between them. Also if you're editing a sentence that extends over 3 lines, you can easily see the whole sentence here and move words among all the lines. Your selected lines get loaded in the editor, as you see above, and the GUI expands based on how much text you load, up to a certain limit. You can still go beyond that limit if you use the Taller GUI button, though.

When you edit the lines, you click on Save, and they get saved back to the subtitle script. Line breaks in the editor determine what one line in the script is, so you can't change the line count - save for one exception. If you load only one line and make line breaks in the editor, these become \N when you save it. This is useful when you're typesetting for example a cell phone sign with long text and need to make line breaks manually in many places.

In the textbox on the right, you can see some useful information: duration, characters per second, and character count for each line. The content of this box is only informative and doesn't save anywhere, so it doesn't matter what you do with it.

You have a number of tools at your disposal. There are 4 buttons to remove things: tags, comments, linebreaks, and leading dash+space. This last one is for removing this garbage from subtitles that have two speakers in one line.

The Add tags button adds the tags you check at the top of the GUI.

The Capitalise button cycles through 3 modes of capitalisation for the whole text: lowercase, uppercase, and titles. For sentence-style capitalisation, there's a Sentences checkbox at the top. It capitalises the first word of the line, first word after ./?/!, days of the week, months, Mr., Dr., and a few languages like "English". It may work well for fixing some mistakes in a text that's supposed to be capitalised correctly, and it may be helpful for text you get all in lowercase or uppercase in the first place, but changing first to lowercase or uppercase and then capitalising like sentences is likely to miss a lot of things, like acronyms, etc. I added some limited support for characters from mostly Germanic languages and French, but it's only about 30 characters. Lua can only replace these one at a time (or at least I don't know of an easier way), and once you go down the road of adding more characters, it never ends. Too many languages, too many weird characters. But it's really easy to add your own if you need, so look for the UP and LO tables in the script.

Then there is a Replace function that has improved a lot in the latest versions. It's quite similar to ctrl+h, but applies to the text loaded in the Editor. I generally use ctrl+h for the whole script and this one for selections. It has a mode for whole word only without requiring that you know regexp. This means replacing "and" won't replace the "and" in "android". It has both standard regexp and lua pattern matching. (If you check both, lua applies.)

A recent addition is the Switch button that switches to another mode. In this mode, you can see Style, Actor, and Effect the same way as Text, and you can edit all of them the same way. Replacer can also be used on all of them. The checkboxes at the top determine what the Replacer will affect. You must always maintain the original line count for each of the 4, or you won't be able to save the results.

Re-Split

Purpose: Move last word to the start of the next line or the other way round

Supports: Handling of line breaks, start tags, and end comments

This exists as a stand-alone script, but it has now been merged into Multi-Line Editor (separate macros) and will be updated only there.

Example (two lines in script):

Those who are exposed to lots of them often go through what you went through.

Press hotkey for 'ReSplit - Forward'.

Those who are exposed to lots of them often go through what you went through.

So as you can see, this is for re-splitting weird lines, mainly ones produced by the infamous Crunchyroll, because they really like their awkward splits.

It applies to active line, so no use selecting more lines. One key moves the last word to the next line, the other moves it back.

This supports start tags and end comments, but not inline tags and possibly other weird stuff. Once a line break gets to one end, it gets nuked (and not transferred to the other line). You won't get it back by shifting the word back.

MultiCopy Download

Purpose: Copy specified things from selected lines / paste things to selected lines

Features: copy any tags; copy between columns; many modes of pasting The main idea is to copy something from X lines and paste it to another X lines.

Copy tags - start tags text - text AFTER start tags (will include inline tags) visible text - text without any tags or comments all - tags+text, i.e. everything in the Text field any tag - copies whatever tag(s) you specify by typing in the textbox, like "org", "fad", "t", or "blur,c,alpha" export CR for pad - copies the text of the whole script in a way suitable for pasting on a pad signs go to top with {TS} timecodes; nukes linebreaks and other CR garbage, fixes styles, etc. colour(s) / alpha - copies colours / alphas selected by checkboxes above

Other copy options should be obvious.

Copy from / to is a quick copy function between columns. Switch switches text in the selected columns. Copying strings to number fields does nothing. Attach attaches data from one column to another. For example if you have "01" in effect, and "Text" in text, and use the settings below, you will get "01 - Text" in text. (Effect remains unchanged.) Checking After would make it "Text - 01". If effect was empty, you'd be just attaching the text you type in "Link".

Once you get your data, click on Copy to clipboard, select the lines you want to paste to, load the GUI again, and click on Paste from clipboard. This will load the saved text in the textbox. You could use ctrl+c / ctrl+v instead of the buttons, but Windows has limits on the length of that, and the buttons bypass that restriction. Now you use one of the three paste buttons, depending on what you're pasting.

Paste tags pastes start tags. Paste text pastes text after start tags. If orig. text has inline tags and pasted text doesn't, the script tries to preserve the tags in 2 ways: 1. It takes the words in orig. text with tags before them and looks for the words in the new text, and if that fails (all would have to match), 2. it counts by words and applies tags to the appropriate place in the line by word count. Some examples (original text --> pasted text --> result): one two {\bord2}three four --> two three four --> two {\bord2}three four one {\bord2}two three {\bord3}four --> one seven two five four six --> one seven {\bord2}two five {\bord3}four six one {\bord2}two three {\bord3}four --> four three two one --> {\bord3}four three {\bord2}two one one {\bord2}two three {\bord3}four --> two three five six --> two {\bord2}three five {\bord3}six (no 'four', so it counts words) one {\bord2}two three {\bord3}four --> what is this --> what {\bord2}is this one {\bord2}two three {\bord3}four --> what is this i don't even --> what {\bord2}is this {\bord3}i don't even one {\bord2}two {\bord3}three --> one two three two one --> one {\bord2}two {\bord3}three {\bord2}two one As you see in the last line, the downside is that if it matches the word multiple times, the tags go to each instance. If pasted text has inline tags, it gets pasted as is.

Paste extra all - This is like regular paste over from a pad, but with checks to help identify where stuff breaks if the line count is different or shifted somewhere. If you're pasting over a script that has different line splitting than it should, this will show you pretty reliably where the discrepancies are. pasteover+ - Apparently the previous function wasn't enough. Lack of communication between the editor and timer can go pretty far. The other day I ran the function above and got this: "The pasted data is 42 lines longer than your selection". A day later I got a discrepancy of 59 lines or so. It took fucking 40 minutes to fix, which is ridiculous because had I expected that, it would have taken me 20 minutes to retime the whole episode. So I had to write something even more insane, and this is it. It still took maybe 10 minutes to fix the above-mentioned script, but it's the fastest solution that I have. Here I would like to point out that pasting over a very line-mismatched text is the most annoying part of fansubbing. So here's the monstrosity in its full glory: On the left is the text of the original script, loaded from Aegisub. On the right is the text being pasted over. You shouldn't touch the text on the left because the line count in Aegisub has to stay the same. Text changes will be ignored too. The left side is for reference so that you can fix the right side.

Here you can see how the two sides don't match at all. (That's part of the script I had to deal with.) I already joined the jackass line, so out of these 8-10 (depending on the side) lines, only 3 are in the same place. What needs to be done is resplitting the lines on the right so that they match the lines on the left.

So how does this work?

First you get a bunch of lines loaded from both texts. The number at the top is how many lines you'll be sending as checked or fixed. So for this image, when you press Send Next, 3 lines from the top of each side will be "sent".

The left is just for reference; the lines from the right get saved for the final pasteover.

You can change the number of sent lines for each turn. More is faster, but when the script is really chaotic, sending by 2-3 makes it clearer what's going on. And even if the text is clear enough, you can just send 3 lines over and over pretty fast.

Add Left will add more lines to the text on the left, based on the number at the bottom left (10).

Add Right does the equivalent for the right side, and obviously, Add Both does both at the same time.

Send Next and Add Both does the sending and adding all at once - as long as there are enough lines to send.

There are info/log fields on each side, normally telling you how many lines remain on each side. You can again see how close 272 and 318 are...

These fields are important because they also report errors, mainly when you're trying to send more lines than you have loaded. The other error is when you reach the end of data on either side and you have different line counts loaded in each box.

When you press the Send button and nothing seems to be happening (nothing was sent), check those fields for the error message.

The last thing here is the Unsend button which will be a life-saver when you fuck up and send something you shouldn't have. This will load the sent lines back (again by the number at the top), and you can correct whatever you had done.

You can modify the numbers depending on how messed up your script is. You can load all lines at once if you feel like, though having only a few more lines than you're sending may look neater and less confusing.

You will still have to deal with the actual line splitting after this, but with really fucked up pasteovers, this has the huge advantage of not having to constantly switch between Aegisub and your browser and scroll all over the place looking for something that looks similar to something in the other window. Basically I put the browser and Aegisub next to each other. It hasn't been extensively tested yet, but I've done a few successful runs, so hopefully it'll work. any tag - This is for pasting tags, in the "\tag1\tag2" format. Doesn't matter whether you copied or typed them. superpasta - This allows copying columns from one selection to another. Just copy several whole lines with ctrl+c, select new lines, paste the whole thing in the GUI, select superpasta, and you'll get this:

You can paste for example Effect from some lines to Effect in other lines, but also to another field in those other lines, like Actor. You can also apply this to the same lines you copied from and copy things between columns. As copying between columns has now been added directly to the main GUI, this is pretty much redundant, but it can do several of those operations at the same time. Checkboxes determine which column should be copied, and dropdown menus determine where it should be copied to. text mod. - This pastes over text while keeping inline tags. If your line is {\t1}a{\t2}b{\t3}c and you paste "def", you will get {\t1}d{\t2}e{\t3}f. This simply counts characters, so if you paste "defgh", you get {\t1}d{\t2}e{\t3}fgh, and for "d", you get {\t1}d. Comments get nuked. gbc text - This is for pasting over lines with gradient by character. You get this: [start tags][pasted text without last character][tag that was before last character][last character of pasted text] For colours, the gradient should be replicated in full. de-irc: paste straight from irc with timecodes and nicknames, and stuff gets parsed correctly. [12:30:24] Dialogue: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,,Text Timecode and nickname gets nuked, and the rest is applied like regular paste over.

If pasted data doesn't match line count of selection, you get choices for what you want to do. If you have more lines selected than you're pasting, you can either Loop paste or Paste only copied lines. In the case above, Loop will paste that one line into all 4 selected, while the other option only pastes that one saved line, and the remaining 3 stay the same. If you had 3 lines copied and 8 lines selected, Loop would paste lines 1 2 3 1 2 3 1 2 into the 8 selected.

If you have more lines copied than selected, you can Paste only what you have copied, or Paste all lines. The former will paste whatever fits into your current selection. The latter will keep pasting to lines after the selection until the clipboard is empty.

You can use Replacer on pasted data. This applies to what you have pasted in that large textbox and allows you to modify the data you're pasting. You can, for example, copy values from one type of tag and paste as another type of tag. Example: copy 'bord', replace 'bord' with 'shad', and paste border values as shadow values. Another example would be copying primary colour, replacing \c with \3c, and pasting as border colour.

Repeat Last - Repeat last action with the same settings. You should press the same button, obviously, or weird things might happen, though in some cases it may be applicable.

Apply Fade Download

Purpose: Create an ordinary fade as well as other fading effects

Features: alpha/colour/blur fades with accel; fade letter by letter; fade across multiple lines; fade in using \ko; fade to/from current frame; remember last settings; save config

Supports: Non-standard characters in Letter by Letter (like ö); merging of fade by letter with existing inline tags

For a regular fade, type only Fade in / Fade out values.

Checking alpha will use alpha transform instead, with the Fade in/out values and accel. Checking from/to colours will do colour transforms (with accel). If only one checked, the other will be alpha transform. Checking blur will do a blur transform with given start and end blur (and accel), using the current blur as the middle value. In case of user stupidity, i.e. blur missing, 0.6 is used as default.

Letter by letter This fades each letter separately, in a sequence. The dropdown menu is the fading time for each letter, while Fade in/out are for the overall fades. So if you have 10 characters and use 120ms/letter and 1200ms Fade in, the fades will follow perfectly one after another. If the Fade in is 1000 ms, they will overlap a little. If 500ms, you'll have about 3 letters fading at a time. rtl fades right to left. Delete removes a letter-by-letter fade (by removing all transforms and alphas).

Letter by letter using \ko uses {\ko#} tags instead of transforms for fade in. If the Fade in is under 40, it's used as \ko[value]; if it's 40+, it's considered to be the overall fade, i.e. when the last letter appears. \ko by word fades in by word instead of by letter. (Inline tags are supported, but if you fade by word and have tags in the middle of a word, it won't work as you want it to. Also, \ko actually works with decimal values, like \ko4.6.)

Fade across multiple lines will create a set of alpha transforms across lines. This can be used if you want to fade out a whole part of a conversation. Like people walking away and talking, sound gets quieter... This nukes all present alpha tags. It supports shadow alpha (\4a). Global time will use times relative to video, rather than of each individual line. This makes a difference with gaps between lines.

Fade in to current frame - sets fade in to current video frame. Fade out from current frame - sets fade out from current video frame. These are for setting fades very easily without requiring any numbers. The current frame will be the first/last fully visible, so for Fade in, set to the first frame after the fade, not the last frame of the fade. Remember last settings - the GUI will remember your last used values (until automation reload).

[Save config] - Saves your current settings as defaults.

Extra functions: Fade between 0 and 1 gives you that fraction of the line's duration, so Fade in 0.2 with 1 second is \fad(200,0). Fade value of 1 is the duration of the whole line. Negative fade gives you the inverse with respect to duration, so if dur=3000 and fade in is -500, you get \fad(2500,0), i.e. 500 from end.

Selectricks Download

Purpose: Select or sort lines based on given criteria

This is explained in detail in this blog post, so I will only address what has been added since then.

If you're selecting by Numbers and use ==, you can set ranges. This means that selecting layers with ==, you can match 4-6, and layers 4, 5, and 6 will be selected.

regexp has been updated to be real regexp and not lua.

Presets lines w/ comments 1/2 - This selects lines that have comments in {these} brackets - mode 1 from all lines, mode 2 from selected lines. same text (contin.) - This reads clean text (no tags/comments) of selected lines and then goes line by line checking if they have the same text as any of the selected ones, adding them to selection. It stops at a line that has a different text from all the selected ones. In other words, it selects all following, continuous lines that match the text of the selected lines. Effectively, this is mainly useful to select a whole mocha-tracked sign or a gradiented sign. same text (all lines) - Same as previous, but applies to the whole script. its/id/ill/were/wont - Selects lines containing these words so that you can make sure they weren't supposed to be it's/i'd/i'll/we're/won't. range of lines - Selects a range of lines based on numbers you see in subtitle grid. Type something like "500-600" in the Match this field. move sel. up - Moves your selection up by 1 line, so if lines 5-8 are selected, lines 4-7 will be selected. move sel. down - Opposite of previous. For both presets, you can move by more lines if you type the number in Match this. move sel. to the top - Moves selected lines to the top of the script. With mod checked, active line/selection doesn't go to the top but stays where it is. move sel. to the bottom - Same as previous (including the mod part) but for bottom of the script. sel: first to bottom - Shifts lines in selection, moving the first selected line to the bottom of the selection. (1-2-3-4 -> 2-3-4-1) sel: last to top - Opposite of previous - last line in selection goes to the top of selection.

The mod checkbox works with two more things. With sort by time it sorts by end time. With OP/ED in style it selects not only by style name, but also all lines that appear (timing-wise) between the start of the first OP or ED line and the end of the last OP/ED line. The purpose is to also select signs that are part of OP/ED but don't have OP/ED in style name.

ShiftCut Download Purpose: Timing operations

Features: cut lead in/out; prevent overlaps; fix overlaps; apply to selected styles; custom presets for keyframe snapping; save config

This started with some things the TPP didn't do and ended up as a complete replacement of TPP, including functions of the shifting tool.

For clarity, let's divide this by colours again.

The top part is general settings.

Apply to selected / Apply to all lines - This should be obvious.

Styles to apply to - There are 3 presets: All, All Default, Default+Alt. All Default applies to all styles with "Defa" in the name. Default+Alt applies to styles with "Defa" or "Alt" in the name, so this will match stuff like "Default Flashback" or "Alternative". Below that, the dropdown menu shows all styles present in your script. The box next to it lets you type an additional style you want to include. This may be useful if one of your dialogue styles has an odd naming pattern.

Info (link/snap) - For linking and keyframe snapping, this gives you information about how many lines were affected. This can be useful when applying something to the whole script, unsure whether your settings are a good idea. If you find no lines were changed, your settings were useless. If too many lines were changed, maybe you did something wrong, etc.

Mark changed lines - Same purpose as the Info above, but this marks the lines in Effect so that you can check the changes.

Lead in/out Simple. Check which of the two you want (or both), set values in milliseconds, go. Cut overrides Add, so it doesn't matter whether Add is checked or not. You can also cut by using Add and negative values. Prevent overlaps from adding leads - This makes sure that applying lead in/out won't create overlaps with adjacent lines. Don't add leads on keyframes - This is useful when you're fixing a script that's already snapped to keyframes. For example if your timer makes short lead outs, you can add 150ms lead out to all dialogue lines with these 2 checkboxes checked pretty safely (and then Link Lines).

Link lines Max gap - Maximum gap between lines to be linked. If the gap is longer, no linking. Bias - Where the lines will be linked. 0.5 is in the midle of the gap. 0.8 means 80% of the gap goes to the first line, 20% to the second. Fix overlaps - This allows you to fix what would be unwanted overlaps. If two consecutive lines overlap by less than this number, they will be made not to, based on the Bias, which works like the one for linking. You can for example set this to only 50 if you want to just fix accidental 1-frame overlaps. (Assuming "normal" frame rates.) If you want to only fix overlaps, set linking gap to 0.

Keyframes This is really just like TPP, so there isn't much to explain. Keyframe settings are in frames, not ms. Preset numbers are in the same order the GUI shows above it. Preventing overlaps is something TPP doesn't have, afaik. Overlaps would happen when lines are linked before a keyframe and your Ends before number is higher than Starts before, for instance. Max CPS - If snapping to a keyframe would result in a CPS higher than the given value, the line won't be snapped. "0" disables this. (Or you can just set a high number.) This setting will allow bleeds if the lines would otherwise be hard to read. However, it only applies if the bleed is over 3 frames because 1/2/3-frame bleeds are just never good and hardly make much difference for readability.

Shift times - This is as straightforward as it gets. Shift backward or forward by milliseconds.

All - This button applies Lead in/out, Link lines, and Keyframe snap.

Save config - Saves your current configuration. This also lets you modify (add/remove) the keyframe presets.

Macro: Shift End Link Forward - Shifts end time by 3 frames forward, along with start time of the following line if linked. Macro: Shift End Link Backward - Same but backward. Macro: Shift Start Link Forward - Same but start time + end time of previous line. Macro: Shift Start Link Backward - You get the idea... These 4 macros are hotkey equivalents of Ctrl+mouse drag. They can be useful for quick-adjusting the point of linking if for whatever reason you prefer the keyboard over the mouse. More useful for checking/correcting timing rather than timing itself.

Time Signs Download

Purpose: Time signs from timecodes like this one: {TS 5:36}

Features: shift times; snap to keyframes; fill in times for lines without timecodes; use multiple timecodes; add blur; save settings

This checks for timecodes like {TS 5:36} in your line and uses them to set start and end times for the line. It can use a bunch of different formats of the timecode, but it's best to stick to this.

Shift timecodes by ... sec - If your translator uses a different raw than the one you typeset to, you can use this to shift the times. For lines without timecodes... - For multi-line signs with timecode only on the first line. Automatically remove {TS ...} comments - Removes coments starting with 'TS', i.e. the timecodes it used for timing. Automatically add blur - You should do that for every sign anyway, so why not now. Snapping to keyframes - Snaps start/end of the sign to nearby keyframes. Set how far it should look for keyframes. Save current settings - Saves a config file.

You can have multiple timecodes for the same sign, like {TS 5:36, 5:47, 6:52}, and the script will create a line for each sign.

Change Case Download

Purpose: Change case of text

Supports: Non-standard characters (é, è, ï)

This is as simple as it gets, and the GUI explains it, but I added some modifications. When you check 'mod':

Words: This will leave words that are already uppercase, so it won't change CIA to Cia. Not that those assholes deserve such nice treatment, but this is about grammar. Lines: This won't capitalize "i", which it normally does, so this can help in other languages. Sentences: This will run lowercase first. It will mess up names and other stuff, so normally you shouldn't use it, but in case you get text in uppercase, this will save you the trouble of running the GUI twice. Lowercase: This will lowercase only words that are in uppercase. If you have weird text that has random uppercase words for emphasis, or those shitty subs with signs in uppercase, this may help.

Significance Download Purpose: Do a shitload of things that the other scripts don't do

Features: import/export signs; make chapters; number lines; motion blur; merge inline tags; reverse text; fake capitals; clone clip; convert \k to \t\alpha; convert framerate; many special effects for styling; info about line; save config

Supports: Non-standard characters for letter-by-letter operations

(Note: Formerly called 'Unimportant', but that name made it seem much less significant than it is. Also, 'Significance' has 'Sign' in it, so it seemed appropriate.)

This is by far my largest script. The only one over 100 KB, abour 20% larger than Relocator. It has a Help button and Help menu, so use those. It's also explained in more detail in this blog post. I might put some more descriptions for some functions here later, but for now, just a brief overview of the sections.

Import/Export part

The whole Import/Export section is just this small part on the left.

import signs / export sign are the main functions. The purpose is to save signs/templates that you're going to use often, and then load them up when you need them, usually replacing the text with text of the new sign.

An imported sign can have any number of lines. When imported, the lines can either be shifted to the start of the current line, or you can match the times of all lines to the current line. You can keep the original text or replace with the curent text, or replace only certain lines and keep text of the others. You can also combine tags of the original and the current lines, with either of them overriding the other if there are the same types of tags.

Chapters part

You can either create lines with chapters or export chapters as xml (or txt).

To create a chapter on the current line, check chapter mark and either select one from the dropdown menu, or if none of them suit your needs, type the name in the edit box below.

The same edit box can be used to enter language for the exported chapters if you don't want the default "eng". Numbers part

Do Stuff part

This was the last part to be added to Significance, but it has since become the largest one by far.

Some of the functions are simple and rarely useful, and they're there 'just in case somebody ever needs them'. Others are very powerful and create complex effects.

They belong in the same category only because it's the "didn't fit anywhere else" category, so this whole section is pretty random.

The only way to get an idea of what's there is to read the whole help for that section, and for some of the more complex ones there's help in their own GUI that you get to when you activate the function.

If you're looking for something unusual and you don't know if it exists, there's a pretty good chance it might be here.

Some of the functions use the Left/Right fields or the Marker, so if you just try them out without reading the help, they may do nothing. Or possibly, they may break something.

FadeWorks This was created for various fbf fade effects that focus more on replicating lines than alpha fade. The idea was to add lines before start time and after end time and do things you can't do with transforms. The main part is shifting position, which includes acceleration, so you can create linear movement with accel or all kinds of other moves.

Each side of the GUI is separate; left for 'fade in', right for 'fade out'. Lines to create is how many lines will be created before and after the current line. If set to '0', the effect for that side is disabled. Frames per line is the duration of each created line. Shift each line by is the timing difference between the lines. This is generally best to leave the same as the setting above it. With values of '1', you get regular fbf lines. Values of '2' would make consecutive 2-frame lines. If the duration is larger than this setting, lines will overlap (which may be used as a special effect). If this setting is larger than the duration, there will be time gaps between the lines (which probably isn't too useful). X/Y distance is where the starting/ending point will be relative to current \pos. If X is -100 and 100 respectively, the line will start 100 pixels to the left of where it is now and end 100 to the right, no matter how many lines you put in between. Acceleration is separate for each element, as that allows for more effects. In fact, the accel on the X/Y movement is one of the main points of this. fbf transform - you can type some tags, like \bord10, and border will be 10 on the outer lines (first of fade in, last of fade out) and transform frame by frame to whatever value you have on the main line. The main line should always remain unchanged. fbf alpha tf is just an extra field to enter alpha values for the above (in hexadecimal, like "FF") to avoid having to type out the whole thing. The field above can actually handle even colours, but you have to type/copy them, as otherwise the GUI would be like Hydra on steroids. The GUI remembers values even if you press Cancel, so you can just leave, copy some values somewhere, get back and paste them in without losing the other data. Again, this has its own accel. Fade mode is pretty weird and hard to explain, but it should be used on lines that have fades. The replicated lines will use those fades, and the idea is to create fading lines that overlap with one another in different positions. This mode disables Frames per line. You can add transforms to this, but just like the fades, these will reset on each line, and the effect is kind of bizarre and requires some experimentation to get something useful out of it. It does work combined with the fbf transforms, but the results may be a bit unpredictable and bad, and they will look different for fade in and fade out.

All of this is much better to show than explain, so I have made this file with some examples. Load it up and see what can be done with this. Then you'll have to experiment a bit to figure it out. Each of those examples is made from the one middle line.

Of course none of this is useful for regular typesetting but rather as special effects for song styling and such.

Masquerade Download

Purpose: Multipurpose

Features: save/load masks; shift tags in line; apply \an or \q2 tags; alpha time signs

Masquerade Creates a mask with the selected shape. from clip takes the shape of the mask from a clip in your line. See here. create mask on a new line does the obvious and raises the layer of the current line by 1. If you select a secondary colour (\2c), this will be then used as primary colour for the mask on new layer. remask only changes an existing mask for another shape without changing tags. Save/delete mask lets you save a mask from active line or delete one of your custom masks. To save a mask, type a name and the mask from your active line will be saved (appdata/masquerade.masks). To delete a mask, type its name or type 'del' and select the name from the menu on the left.

Shift Tags Allows you to shift tags by character or by word. For the first block, single tags can be moved right. For inline tags, each block can be moved left or right.

This mini GUI has start tags on the left and inline tags on the right. Check those you want to shift. Click on Shift Left or Shift Right. Check word if you want to shift by words instead of letters, and set how many letters/words to shift by. remove selected tags deletes them instead of shifting. All Inline Tags checks the checkboxes above it. You can create regular start tags quickly and use this to shift them to become inline tags. an8 / q2 - Applies selected tags.

Motion Blur Creates motion blur by duplicating the line and using some alpha (@). You can set a value for blur or keep the existing blur for each line (Keep current). dist is the distance between the \pos coordinates of the resulting 2 lines. If you use 3 lines (3 L), the 3rd one will be in the original position, i.e. in the middle. The direction is determined from the first 2 points of a vectorial clip (like with clip2frz/clip2fax).

Merge Tags Select lines with the SAME TEXT but different tags, and they will be merged into one line with tags from all of them. For example:

{\bord2}AB{\shad3}C A{\fs55}BC -> {\bord2}A{\fs55}B{\shad3}C

If 2 lines have the same tag in the same place, the value of the later line overrides the earlier one. alpha shift Makes text appear letter by letter on frame-by-frame lines using alpha&HFF& like this:

{alpha&HFF&}text t{alpha&HFF&}ext te{alpha&HFF&}xt tex{alpha&HFF&}t text

The original lines must all have the alpha tag at the beginning.

Alpha Time Either select lines that are already timed for alpha timing and need alpha tags, or just one line that needs to be alpha timed.

In the GUI, split the line by hitting Enter where you want the alpha tags. If you make no line breaks, text will be split by spaces. Alpha Text is for when you have the lines already timed and just need the tags. Alpha Time is for one line. It will be split to equally long lines with alpha tags added. If you add "@" to your line first, alpha tags will replace the @, and no GUI will pop up. Example text: This @is @a @test.

Strikealpha Replaces strikeout or underline tags with \alpha&H00& or \alpha&HFF&. Also @.

@ -> {\alpha&HFF&} @0 -> {\alpha&H00&} {\u1} -> {\alpha&HFF&} {\u0} -> {\alpha&H00&} {\s0} -> {\alpha&HFF&} {\s1} -> {\alpha&H00&} @E3@ -> {\alpha&HE3&} NecrosCopy Download

Purpose: Multipurpose

Features: apply \fax easily; copy things from one line to others; split by linebreaks

(Note: Formerly 'Fax This' / 'CopyFax This'. Renamed because I got bored with those names.)

Fax It Adds a \fax tag. to the right just adds a "-" (minus). (Yes, that's pretty useless.) from clip calculates the fax value from the first two points of a vector clip. I the clip has 4 points, points 3-4 are used to calculate fax for the last character and a fax gradient by character is made. \fscx, \fscy, and \frz are supported.

Copy functions All copy functions copy things from the first selected line to the other selected lines. If the other lines already have the thing that's being copied, it gets replaced, of course.

Copy Stuff This lets you copy almost anything from one line to others. The primary use is to copy from the first line of your selection to the others. If you need to copy to a line that's above the source line in the grid, just click Copy with the selected things and then use Paste Saved on the line(s) you want to copy to.

The GUI always loads data from the first selected line (not active line). Check what you want to copy from this line to the other lines in your selection. The tags on the left are start tags; on the right are inline tags. Inline tags will only be pasted to the first tag block. As you can see, you can also copy Start Time, End Time, Style, and Text. If you select only one line, check some things, and click Copy, this will be saved in memory. (It's the script's memory, not clipboard, so reloading automation nukes it, but you can use clipboard for other things.) You can then select one or more other lines and click Paste Saved, and the things saved in memory will be applied to those lines. You can also copy tags inside one line. This is similar to Shift Tags, but it leaves the tag in the original position too. So you can type an asterisk (*) before "kill", check "\blur0.8", and the blur tag will be copied there. Copy tags after all linebreaks copies selected tags after all linebreaks in all selected lines. This is useful when you have gradient by character and linebreaks - you need the gradient for each "line" separated by linebreaks, so you copy the needed start tag after each linebreak. [Un]hide lets you hide/unhide checked tags (by making them comments). If you check a tag, it gets hidden. If you don't check anything, whatever was hidden gets unhidden. This way you can hide something that you want to reuse later. Good for clips, for example.

{\fad(2000,0)\an6\blur0.6\pos(980,108)}Azure Waters Check "\fad(2000,0)", click [Un]hide: {\an6\blur0.6\pos(980,108)}Azure Waters{//fad(2000,0)} The fade is now just a comment. If you run the script again, check nothing, and click [Un]hide, the line will return to its previous state.

Copy Tags Copies the first block of tags in its entirety from first selected line to the others.

Copy Text Copies what's after the first block of tags from first selected line to the others (including inline tags). Copy Clip Copies clip from first selected line to the others. shift clip every frame will shift the clip by that amount each line.

Copy Colours Copies checked colours from first selected line to the others. Unlike Copy Stuff, this can read the colours from the style when tags are missing. You can also include alpha for the checked colours.

3D Shadow Creates a 3D-effect out of a shadow by making multiple layers with different shadow size. Uses primarily xshad and yshad.

Split by \N Splits a line at each linebreak. If there's no linebreak, you can split by tags or spaces. If you want to split by tags or spaces when you have linebreaks, check the split GUI option.

Splitting will try to keep the position of each part, but it only supports \fs, \fscx, \fscy, and \an. I decided not to add a \pos tag when there isn't one. \fscx and \fscy seem to work fine. \fs works fine with linebreaks, but it has some issues with spaces/tags. In general, you can always expect some small inaccuracies wit the positioning.

Encode - Hardsub Download

Purpose: Encode a clip or the whole video with or without hardsubs, using x264 encoder

Requirements: - x264.exe (8-bit and/or 10-bit depending on what you want to do. Mocha clips must be 8-bit.) - vsfilter.dll / vsfiltermod.dll for hardsubbing - avisynth (not required when encoding for mocha)

Buttons: Encode - Creates scripts for encoding with settings from the GUI. It will ask if you want to encode now or later. x264 - Navigate to where your 8-bit x264.exe is. The path will appear in the top line of the GUI. x264 10bit - Same for 10-bit x264. vsfilter - Same for vsfilter.dll. vsfiltermod - Same for vsfiltermod.dll. Target - If Target folder is Custom, then this loads the path to the folder where you want your encode to go. The dialog only select files, so you have to select a file in that folder (or just type the path in the GUI). Secondary - Select secondary subtitles. Enc. Set. - Shows a list of recently used encoding settings to choose from. Save - Saves settings.

Other Options: Source video - The video from which you're encoding. This will automatically show the name of the video you have loaded in Aegisub. Target folder - If Same as source, then the encode will be where your source video is. If Custom, then you need to specify a path. Encode name - Specify which extension you want, and adjust the encode's name if needed. Primary subs - Choose which filter you want to use. The file you have loaded in Aegisub should show up there. Secondary - Check this if you want two subtitle files, choose filter, and use the Secondary button to load a file. Encoder settings - Settings that will be used for encoding. Change as you wish. Settings 4 mocha - Settings that will be used instead if you check Encode clip for mocha. Trim from / to - First and last frame of the clip you want to encode. If unchecked, whole video is encoded. Mux with audio - Includes audio in the encode. Encode clip for mocha does automatically the following (meaning you don't have to do those manually): - disables 10bit - enables trimming - disables subtitles - sets target to .mp4 - disables avisynth use 10 bit - Uses the 10 bit x264 binary specified in the settings at the top. Delete batch file after encoding - Deletes the encoding batch file when done. Delete avisynth script - Same for the avisynth script. Delete A/V after muxing - Deletes temporary audio/video files when muxing with audio. Keep cmd window open - Keeps the cmd window open when encoding's done. This allows you to see what errors there were if encoding doesn't work.

You can encode clips for mocha with this if torque's Motion script fails to encode for some reason, or you can use it for easy hardsubbing.

Multiplexer Download

Purpose: Mux video with subtitles, suitable for muxig fansub releases

Features: create CRC; create xdelta; automatically use group tag; two subtitle files; mux chapters

Half of this script is documentation, so there's really nothing to add here. Runemap Download

Purpose: Show a list of hiragana/katakana with corresponding romaji

This was mainly a fun experiment to see if I can do this. (It was very tedious.) If you're typesetting and don't know which sign is which, this can help you identify them. If you type hiragana/katakana/romaji words in the corresponding fields and click on Transcribe, you should get the other two. It's probably buggy, and I probably won't fix anything much about it.

Backup Checker Download

Purpose: Save a backup of your script so that you can later check the original lines after you've edited them

The GUI looks pretty much like Multi-line Editor, just with different functions. Load from Memory - Loads lines from memory, which is also what gets loaded by default (if you saved it before). Load from File - Loads lines from the file with the filename you see. (Type to change if you want from a different one.) Save to Memory - Saves the current script to memory. (This gets erased if you reload automation scripts.) Save to File - Saves it to a file with the name you see there, in the .ass script's folder. (You can make any number of these files.) Memory to File - Saves the content of memory to a file. File to Memory - Loads the content of a file to memory. No Comments - Removes {comments}. You can easily switch between different backups. If you split/join lines, the backup will be off by those, but you can just select more lines to load the ones you need to see. list of functions of unanimated's Aegisub scripts aka "Is there a script that does ...... ?" (All scripts include detailed descriptions.)

HYDRA add any tags add inline tags apply tags to sections of text apply tags before every character / word add transforms (new transforms, add to existing transforms) relative transforms (transform by, rather than transform to) increase value of applied tags for each line create a gradient by character create a vertical/horizontal gradient create a gradient by line copy fscx value to fscy and vice versa move inline colour tags to the start (when you use the eyedropper tool but cursor isn't at the start of the editbox) convert between clip and iclip convert \s1 to selected tags and \s0 to the original state of those tags clean up tags / remove garbage / round numbers sort tags in set order (user defined) transform between two sets of tags back and forth (back and forth transform) select overlaps convert clip to drawing convert drawing to clip create a checkerboard pattern clip create a 3D effect from a shadow (needs \xshad\yshad, creates layers to make a transition from the base to the shadow) split line in (2 or) 3 parts based on given timecodes/durations

Hyperdimensional Relocator - Repositioning Field align signs along the same x or y coordinate mirrored image / duplicate across the screen calculate \fax from \org calculate \fax from \clip, including different values on each end + gradient in between calculate \frz from \clip, including averaging between two lines reposition a line based on 2 clip coordinates shake / randomize position (+rotation) create a shadow layer instead of \shad switch progression of pos X and Y for fbf lines (right<->down, up<->left, etc.) replicate a line X times over a given distance fbf retrack - adjust positions of fbf lines (smoothen)

Hyperdimensional Relocator - Soul Bilocator create 1 line with \move from 2 static lines with \pos, including creating transforms (transmove) fix \move coordinates so that sign moves in a straight line (horizontal, vertical) make lines with \pos move the same as a reference line with \move (multimove) reverse \move change starting coordinates of \move by a given amount (shiftstart) change ending coordinates of \move by a given amount (shiftmove) set target coordinates for \move (move to) set target coordinates for \move from a clip (clip2move) create a clip transform that follows \move (move clip) randomize \move, either in the same direction but different distance, or within a given radius of the start/end points

Hyperdimensional Relocator - Morphing Grounds round values for pos, move, org, clip, masks split/chop a line into fbf (frame by frame) segments, changing move to pos and transforms to single tags (line2fbf) join fbf lines by a given number, like join each 3 lines into 1 nuke timecodes in \move or \t (killmovetimes/killtranstimes) set timecodes in \move to first/last frame of the line (fullmovetimes) set timecodes for a transform to first/last frame of the line (fulltranstimes) shift a vectorial clip based off \pos tags for fbf lines (move v. clip) set \org tag with current \pos coordinates (set origin) calculate origin point (\org) from a clip matching a tetragon on screen (calculate origin) calculate/create a clip transform (transform clip) set a \frz tag with predefined values like 30, 45, 60, 90, etc (FReeZe) change rotation by 180 degrees for \frz (rotate 180) change rotation by 180 degrees for \frx (flip hor.) change rotation by 180 degrees for \fry (flip vert.) change rotations to negative number with the same result, like \frz350 -> \frz-10, to help with transforms (negative rot) convert between rectangular and vectorial clips set position in the middle of a rectangual clip (find centre) resize masks in x/y direction (extend mask) flip a mask horizontally (flip mask) change the shape of a mask by randomizing the points (randomask) randomize the values of a given tag to create chaotic effects for gbc or fbf (randomize...) make vertical text by adding linebreaks between letters (letterbreak) add linebreaks between words (wordbreak)

Hyperdimensional Relocator - Cloning Laboratory copy pos, move, org, clip tags from one line to others this includes stacking/combining clips in various ways

Hyperdimensional Relocator - Teleportation shift pos, move, org, clip, mask by a given amount 'mod' can shift by an additional amount for each new line

Colorize (everything selectable for \c, \2c, \3c, \4c) alternate between 2-5 colours letter by letter / word by word shift colours by a letter / word set a given mumber of colours (2-10) evenly across the line (3 = start, middle, before last character) adjust RGB channels (options: only tags outside transforms, only tags inside transforms, all) adjust Hue/Saturation/Lightness (options: only tags outside transforms, only tags inside transforms, all) randomize RGB/HSB (each of the 6) match colours between primary, outline, and shadow create RGB and HSB colour gradients tune/adjust existing colours in the line (options: only tags outside transforms, only tags inside transforms, all) tune/adjust/replace existing colours in current selection

Blur and Glow blur for signs with a border create another layer with glow create double border raise layer for multiple lines

Script Cleanup delete comments delete tags delete commented or empty lines clear actor / effect field raise dialogue layer by 5 clean up tags / remove garbage / round numbers delete specific tags or groups of related tags (colours, alphas, rotations...) delete specific tags only from transforms remove linebreaks

Recalculator multiply values of tags (options: only tags outside transforms, only tags inside transforms, all) add to or subtract from values of tags (options: only tags outside transforms, only tags inside transforms, all) resize clips / drawings shift karaoke (by changing value for the first \k tag in line) recalculate gradients for already existing tags

Selectricks select or sort lines by text, actor, effect, visible text, layer duration, word count, character count, cps, blur, margins reverse order of selection sort by width of text (in pixels) move the last line in a selection to the top of selection and vice versa select lines with the same text as the current one move selection up or down Multi-Line Editor edit multiple lines at once (text, style, actor, effect) replace function for selected lines, including regexp check duration, character count, and cps of a line count characters / words in a selection change case / capitalisation move the last word on active line to the start of next line move the first word on the next line to the end of active line

MultiCopy copy from / paste to selected lines: initial tags, text after tags, both, clip, pos, blur, bord, colours, alpha, fscx, fscy, layer, duration, actor, effect, comments export a crunchyroll script for pad paste over with check for errors paste over from irc (automatically removes timecodes and nicknames) paste over for lines with GBC (keeps the tag before last character so you can just rerun GBC after that. replicates colour gradients in full.) paste the same tags / text to multiple lines paste over of selected fields (layer, effect, text...) copy from one column (layer, effect, text...) to another

Time Signs

ShiftCut (TPP with more options) add or cut lead in or lead out shift times link lines fix overlaps snap to keyframes shift selection so that the active line ENDS at current frame

Apply fade create alpha transforms from fade create colour transforms from fade create blur transforms from fade create fades letter by letter, including right to left make text appear letter by letter or word by word using \ko create a fade across multiple lines set fades as fractions of duration set negative fades (counting from the other end of the line) set fade in/out to current video frame

Change Case change text to lowercase / uppercase capitalise lines, sentences, or titles

Significance export complex signs to a saved file (any number of lines, basically works like a template) import signs from a saved file import chapters from xml a complex function to update lyrics make chapters (includes subchapters) set chapter markers (like make selected line a 'Part A' chapter) number lines in actor/effect field (also layers) by various patterns add markers to numbers in actor/effect (like ab01cd) lua replacer - replace function with regexp perl replacer - same with full regexp lua calculator - perform basic math operations on matched numbers (for example multiply font size by 1.5) jump to the next line with a different actor/effect/layer/style/text than the current one make text appear letter by letter using alpha (shift alpha FF tag line by line) merge two lines with the same text but different tags to include all tags from both lines add a given comment at the end of all selected lines add a list of comments line by line uncomment commented text (nuke { }) switch commented / visible text reverse text character by character reverse text word by word reverse transforms (\bord3\t(\bord5) -> \bord5\t(\bord3); only initial tags, one transform per tag) fake capitalisation of uppercase text using higher \fs for the first letter of each word format dates to one of 4 patterns: "January 1", "January 1st", "1st of January", "1st January" split into letters (one line per character; other characters are alpha'd out) explode (split into letters + characters randomly move and fade away) dissolve text (make text disappear in segments in various ways) duplicate and shift lines - for all selected lines, adds one or multiple frames before and/or after randomized transforms - different values for each line (includes randomized fade, duration, move) clone clip (replicates an existing clip given amount of times in rows/columns) transform kara tags (\k, \kf, \ko) into alpha transforms (FF->00) adjust kara tags for split lines (karaoke with fbf) motion blur comment honorifics convert framerate stats: video name, script resolution, colorspace, styles used, # of dialogue lines, # of selected lines, selection duration active line statistics: # in script, style, font, weight, fnot size, border, shadow, duration, characters, cps, default position

Masquerade create a mask with a predefined shape (includes 2 alignment grids) create a mask from clipped area save/load custom masks shift tags left or right by letters or words motion blur merge tags for 2 lines with same text but different inline tags (including gradients) make text appear letter by letter using alpha (shift alpha FF tag line by line) alpha time line(s) (splits text by given markers, times evenly) transform underline/strikeout tags into alpha tags, as well as @30@ -> \alpha&H30& etc. (strikealpha)

NecrosCopy set \fax calculate \fax from a clip, including different values on each end + gradient in between copy initial block of tags from line 1 to others copy text after initial block of tags from line 1 to others copy clip, shifting it by a given value line by line copy colours from line 1 to others copy anything available from one line to others copy tags from start tags to after linebreaks create a 3D shadow split by linebreaks (keeps times; also splits by spaces and by tags; keeps position for segments under common conditions)

Line Breaker insert linebreak in a meaningful place shift linebreak by 1 word (right or left)

Join / Split / Snap join active line with the following one (tries to handle text / tags intelligently) split a line at first linebreak / period / excl./quest. mark / comma, and adjust timing for each line snap selected lines to keyframes / adjacent lines based on numbers in settings (TPP snapping for hotkeying)

Reset Style choose a style to set for \r tag

Reset-to-tags change \r[style] to tags with the [style]'s properties

Aladin's Lamp deal with right-to-left tags by: - fixing inline tags - fixing line breaks - fixing punctuation Encode - Hardsub encode a clip for mocha hardsub a clip or whole video using one or two subtitle files mux audio for encoded clip

Multiplexer mux video and subs currently opened in Aegisub mux additional subtitle file and/or chapters create CRC for the filename of muxed file create xdelta to patch the source video to the muxed one -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#hydra script_name="HYDRA" script_description="A multi-headed typesetting tool" script_author="unanimated" script_url1="http://unanimated.xtreemhost.com/ts/hydra.lua" script_url2="https://raw.githubusercontent.com/unanimated/luaegisub/master/hydra.lua" script_version="5.0" script_namespace="ua.HYDRA" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="5.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' clipboard=require("aegisub.clipboard") order="\\r\\fad\\fade\\an\\q\\blur\\be\\bord\\shad\\fn\\fs\\fsp\\fscx\\fscy\\frx\\fry\\frz\\c\\2c\\3c\\4c\\alpha\\1a\\2a\\3a\\4a\\ xbord\\ybord\\xshad\\yshad\\pos\\move\\org\\clip\\iclip\\b\\i\\u\\s\\p" noneg2="|bord|shad|xbord|ybord|fs|blur|be|fscx|fscy|"

-- HYDRA HEAD 9 -- function hh9(subs,sel) -- get colours + tags from input getcolours() shft=res.int tags="" tags=gettags(tags) transform=tags ortags=tags ortrans=transform retags=tags:gsub("\\","\\\\") z0=-1

for z,i in ipairs(sel) do progress("Hydralizing line: "..z.."/"..#sel) prog=math.floor((z+0.5)/#sel*100) aegisub.progress.set(prog) line=subs[i] text=line.text linecheck()

if not text:match("^{\\") then text="{\\hydra}"..text end

-- tag position place=res.linetext or "" if place:match("*") then pl1,pl2,pl3=place:match("(.*)(%*)(.*)") pla=1 else pla=0 end if res.tagpres~="--- presets ---" and res.tagpres~=nil then pla=1 end

-- transforms if trans==1 and GO then z0=z0+1 tin=res.trin tout=res.trout if res.tend then tin=line.end_time-line.start_time-res.trin tout=line.end_time-line.start_time-res.trout end if tmode==1 then if res.int~=0 then TF=shft*z0 else TF=0 end tnorm="\\t("..tin+TF..","..tout+TF..","..res.accel..",\\alltagsgohere)}" if place:match("*") then initags=text:match("^{\\[^}]-}") or "" orig=text replace=place:gsub("%*","{"..tnorm) v1=text:gsub("%b{}","") v2=replace:gsub("%b{}","") if v1==v2 then text=initags..textmod(orig,replace) end else text=text:gsub("^({\\[^}]*)}","%1"..tnorm) end end if tmode==2 then text=text:gsub("^(.-\\t%b())",function(t) return t:gsub("%)$","\\alltagsgohere)") end) end if tmode==3 then text=text:gsub("(\\t%b())",function(t) return t:gsub("%)$","\\alltagsgohere)") end) end if tmode==4 then if res.int~=0 then tagtab={} for tt in text:gmatch(".-{\\[^}]*}") do table.insert(tagtab,tt) end END=text:match("^.*{\\[^}]*}(.-)$") for t=1,#tagtab do sf=t-1 tagtab[t]=tagtab[t]:gsub("({\\[^}]*)}","%1\\t("..tin+shft*sf..","..tout+shft*sf..","..res.accel..",\\alltagsgohere)}") end nt=END for a=#tagtab,1,-1 do nt=tagtab[a]..nt end text=nt else text=text:gsub("({\\[^}]*)}","%1\\t("..tin..","..tout..","..res.accel..",\\alltagsgohere)}") end end if res.add and res.add~=0 then transform=addbyline(transform,ortrans) end if tmode<4 and res.relative then stags=text:match(STAG) or "" for tag,val in transform:gmatch("(\\%a+)([%d%.%-]+)") do if stags:match(tag) then oldval=stags:match(tag.."([%d%.%-]+)") transform=transform:gsub(tag..esc(val),tag..oldval+val) end end end text=text:gsub("\\alltagsgohere",transform) if tmode==4 and res.relative then text=text:gsub(ATAG,function(tg) for tag,val in transform:gmatch("(\\%a+)([%d%.%-]+)") do if tg:match(tag) then oldval=tg:match(tag.."([%d%.%-]+)") transform2=transform:gsub(tag..esc(val),tag..oldval+val) tg=tg:gsub("(.*\\t.-)"..transform,"%1"..transform2) end end return tg end) end text=text:gsub("\\t%(0,0,1,","\\t(") :gsub("\\t(%b())",function(tr) return "\\t"..duplikill(tr) end)

-- non transform, ie. the regular stuff elseif GO then z0=z0+1 -- temporarily block transforms text=text:gsub(ATAG,function(tg) return duplikill(tg) end) :gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end)

if res.add and res.add~=0 then tags=addbyline(tags,ortags) end

if tags~="" then if pla==1 then initags=text:match(STAG) or "" orig=text v1=orig:gsub("%b{}","") -- BEFORE LAST CHARACTER if res.tagpres=="before last char." then text=text:gsub("({\\[^}]-)}(.)$","%1"..tags.."}%2") if orig==text then text=text:gsub("([^}])$","{"..tags.."}%1") end if orig==text then text=text:gsub("([^}])({[^\\}]-})$","{"..tags.."}%1%2") end -- SOMEWHERE IN THE MIDDLE elseif res.tagpres=="in the middle" or res.tagpres:match("of text") then clean=text:gsub("%b{}","") :gsub("%s?\\[Nn]%s?"," ") text=text:gsub("%*","_ast_") lngth=math.floor(clean:len()*fak) text="*"..text text=text:gsub("%*({\\[^}]-})","%1*") m=0 if lngth>0 then repeat text=text:gsub("%*(%b{})","%1*") :gsub("%*(.)","%1*") :gsub("%*(%s?\\[Nn]%s?)","%1*") m=m+1 until m==lngth end text=text:gsub("%*","{"..tags.."}") :gsub("({"..esc(tags).."})(%b{})","%2%1") :gsub("_ast_","*") -- PATTERN elseif res.tagpres=="custom pattern" then pl1=esc(pl1) pl3=esc(pl3) text=text:gsub(pl1.."({\\[^}]-)}"..pl3,pl1.."%1"..tags.."}"..pl3) if orig==text then text=text:gsub(pl1..pl3,pl1.."{"..tags.."}"..pl3) end -- SECTION elseif res.tagpres=="section" then tags2="" for tg in tags:gmatch("\\%d?%a+") do txt1=text:match("^.-"..esc(place)) or "" local tg2=txt1:match("^.*("..tg.."[^\\}%a]+).-$") or tg tags2=tags2..tg2 end text=text:gsub("^(.-)("..esc(place).."%s*)(.*)$","%1{"..tags.."}%2{"..tags2.."}%3") -- CHARACTER elseif res.tagpres=="every char." then replace=re.sub(text:gsub("%b{}",""),"([\\w[:punct:]\\s])","{"..retags.."}\\1") replace=replace:gsub("%b{}%s%b{}\\%b{}N"," \\N"):gsub("%b{}\\%b{}N","\\N") v2=replace:gsub("%b{}","") if v1==v2 then text=initags..textmod(orig,replace) end -- WORD elseif res.tagpres=="every word" then replace=text:gsub("%b{}",""):gsub("%S+","{"..tags.."}%1"):gsub("(%b{})\\N","\\N%1") v2=replace:gsub("%b{}","") if v1==v2 then text=initags..textmod(orig,replace) end -- TEXT POSITION elseif res.tagpres=="text position" then v2=text:gsub("%b{}","") pmax=re.find(v2,".") pos=tonumber(place:match("^%-?%d+")) or 0 addpos=tonumber(place:match(".([%+%-]%d+)")) or 0 if pos<0 then pos=#pmax+pos end split=pos+addpos*z0 if split<0 then split=0 end if split<#pmax then be4=re.sub(v2,"^(.{"..split.."}).*","\\1") aft=re.sub(v2,"^.{"..split.."}","") text=be4.."{"..tags.."}"..aft if v1==v2 then text=initags..textmod(orig,text) end end else -- AT ASTERISK POINT replace=place:gsub("%*","{"..tags.."}") v2=replace:gsub("%b{}","") if v1==v2 then text=initags..textmod(orig,replace) end end text=tagmerge(text) :gsub("{(\\[^}]-)}\\N{(\\[^}]-)}","\\N{%1%2}") else -- REGULAR START TAGS for t in tags:gmatch("\\%d?%a[^\\]*") do text=addtag3(t,text) end end text=text:gsub(ATAG,function(tg) return duplikill(tg) end) end

-- strikeout if res.strike then if text:match("^{[^}]-\\s[01]") then text=text:gsub("\\s([01])",function(a) return "\\s"..(1-a) end) else strik=text:match("\\s([01])") or "1" text=text:gsub("\\s([01])",function(a) return "\\s"..(1-a) end) :gsub("^({\\[^}]*)}","%1\\s"..strik.."}") end end -- underline if res.under then if text:match("^{[^}]-\\u[01]") then text=text:gsub("\\u([01])",function(a) return "\\u"..(1-a) end) else unter=text:match("\\u([01])") or "1" text=text:gsub("\\u([01])",function(a) return "\\u"..(1-a) end) :gsub("^({\\[^}]*)}","%1\\u"..unter.."}") end end -- bold if res.bolt then if text:match("^{[^}]-\\b[01]") then text=text:gsub("\\b([01])",function(a) return "\\b"..(1-a) end) else bolt=text:match("\\b([01])") or "1" text=text:gsub("\\b([01])",function(a) return "\\b"..(1-a) end) :gsub("^({\\[^}]*)}","%1\\b"..bolt.."}") end end -- italics if res.italix then if text:match("^{[^}]-\\i[01]") then text=text:gsub("\\i([01])",function(a) return "\\i"..(1-a) end) else italix=text:match("\\i([01])") or "1" text=text:gsub("\\i([01])",function(a) return "\\i"..(1-a) end) :gsub("^({\\[^}]*)}","%1\\i"..italix.."}") end end -- \fad if res.fade then IN=res.fadin OUT=res.fadout fGO=1 if res.glo then if z<#sel then OUT=0 end if z>1 then IN=0 end if IN==0 and OUT==0 then fGO=0 end end text=text:gsub("\\fad%([%d%.%,]-%)","") if fGO==1 then text=text:gsub("^{\\","{\\fad("..IN..","..OUT..")\\") end end -- \q2 if res.q2 then if text:match("\\q2") then text=text:gsub("\\q2","") else text=text:gsub("^{\\","{\\q2\\") end end -- \an if res.an1 then if text:match("\\an%d") then text=text:gsub("\\an(%d)","\\an"..res.an2) else text=text:gsub("^{\\","{\\an"..res.an2.."\\") end end -- raise layer if res.layer then if lay+res.layers<0 then t_error("Layers can't be negative.") else lay=lay+res.layers end end

-- unblock transforms text=text:gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end)

end -- the end

text=text:gsub("\\hydra","") :gsub("{}","") :gsub("\\t%([^\\%)]-%)","") line.text=text line.layer=lay subs[i]=line end end function getcolours() col={} alfalfa={} for c=1,4 do colur=res["c"..c]:gsub("#(%x%x)(%x%x)(%x%x).*","&H%3%2%1&") table.insert(col,colur) if res.alfas then alpa=res["c"..c]:match("#%x%x%x%x%x%x(%x%x)") if alpa~=nil then table.insert(alfalfa,alpa) if res["k"..c] then res["arf"..c]=true res["alph"..c]=alfalfa[c] end end end if res.aonly then res["k"..c]=false end end end function gettags(tags) if res.reuse then if lastags then if res.show then msgbox(lastags) end return lastags else t_error("No tags to reuse",1) end end if res["blur1"] then tags=tags.."\\blur"..res["blur2"] end if res["be1"] then tags=tags.."\\be"..res["be2"] end if res["bord1"] then tags=tags.."\\bord"..res["bord2"] end if res["shad1"] then tags=tags.."\\shad"..res["shad2"] end if res["fs1"] then tags=tags.."\\fs"..res["fs2"] end if res["spac1"] then tags=tags.."\\fsp"..res["spac2"] end if res["fscx1"] then tags=tags.."\\fscx"..res["fscx2"] end if res["fscy1"] then tags=tags.."\\fscy"..res["fscy2"] end if res["xbord1"] then tags=tags.."\\xbord"..res["xbord2"] end if res["ybord1"] then tags=tags.."\\ybord"..res["ybord2"] end if res["xshad1"] then tags=tags.."\\xshad"..res["xshad2"] end if res["yshad1"] then tags=tags.."\\yshad"..res["yshad2"] end if res["frz1"] then tags=tags.."\\frz"..res["frz2"] end if res["frx1"] then tags=tags.."\\frx"..res["frx2"] end if res["fry1"] then tags=tags.."\\fry"..res["fry2"] end if res["fax1"] then tags=tags.."\\fax"..res["fax2"] end if res["fay1"] then tags=tags.."\\fay"..res["fay2"] end if res["k1"] then tags=tags.."\\c"..col[1] end if res["k2"] then tags=tags.."\\2c"..col[2] end if res["k3"] then tags=tags.."\\3c"..col[3] end if res["k4"] then tags=tags.."\\4c"..col[4] end if res["arfa"] then tags=tags.."\\alpha&H"..res["alpha"].."&" end if res["arf1"] then tags=tags.."\\1a&H"..res["alph1"].."&" end if res["arf2"] then tags=tags.."\\2a&H"..res["alph2"].."&" end if res["arf3"] then tags=tags.."\\3a&H"..res["alph3"].."&" end if res["arf4"] then tags=tags.."\\4a&H"..res["alph4"].."&" end lastags=tags if res.show then msgbox(tags) end if res["moretags"] and res["moretags"]~="\\" then tags=tags..res["moretags"] end return tags end function addbyline(tags,ortags) tags=ortags:gsub("\\(%a%a+)([%d%.%-]+)",function(t,v) if t~="an" and t~="fn" then nv=rnd2dec(v+res.add*z0) if nv<0 and noneg2:match("|"..t.."|") then nv=0 end return "\\"..t..nv else return "\\"..t..v end end) return tags end function linecheck() lay=line.layer sty=line.style act=line.actor eff=line.effect GO=nil local lGO,sGO,aGO,eGO if res.applay=="All Layers" or tonumber(res.applay)==lay then lGO=true end if res.applst=="All Styles" or res.applst==sty then sGO=true end if res.applac=="All Actors" or res.applac==act then aGO=true end if res.applef=="All Effects" or res.applef==eff then eGO=true end if lGO and sGO and aGO and eGO then GO=true end if loaded<3 then GO=true end end

-- GRADIENTS -- function hydradient(subs,sel) GT=res.gtype:match("^....") strip=res.stripe acc=res.accel styleget(subs) getcolours() tags="" tags=gettags(tags) if tags=="" then ak() end ortags=tags retags=tags:gsub("\\","\\\\") gcpos=res.linetext gcl=nil if res.middle and gcpos:match("*") then gc1,gc2=gcpos:match("^(.-)%*(.-)$") gcl=gc1:len() end GBCn=tonumber(gcpos:match("^%d$")) or 1 if GT=="by l" then GBL=0 z1=0 for z,i in ipairs(sel) do line=subs[i] linecheck() if GO then GBL=GBL+1 end end table.sort(sel,function(a,b) return a>b end) end for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text orig=text visible=text:gsub("%b{}","") text=text:gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end) :gsub("\\1c","\\c") initags=text:match(STAG) or "" sr=stylechk(line.style) linecheck()

-- hori/vert if GO and GT:match("r") then x1,y1,x2,y2=initags:match("clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)") if not x1 then t_error(res.gtype.." gradient: Missing rectangular clip on line "..z,1) end x1=math.floor(x1) y1=math.floor(y1) x2=math.ceil(x2) y2=math.ceil(y2) if GT=="vert" then total=math.ceil((y2-y1)/strip) else total=math.ceil((x2-x1)/strip) end if total<2 then t_error("This won't create any gradient.\nDecrease the pxl/stripe setting.",true) end

for l=1,total do L=l count=total half=math.ceil(total/2) if res.middle then count=half if L>half then L=total-L+1 end end stags=initags text2=text for tg,V2 in tags:gmatch("(\\%d?%a+)([^\\]+)") do V1=initags:match("^{[^}]-"..tg.."([%d%-&][^\\}]*)") or tag2style(tg,sr) if tg:match("fr") and res.short then V1=shortrot(V1) end if tg:match("\\[fbs]") then VC=numgrad(V1,V2,count,L,acc) end if tg:match("\\[%dac]") then VC=acgrad(V1,V2,count,L,acc) end stags=addtag3(tg..VC,stags) stags=stags:gsub("clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(a,b,c,d) if GT=="vert" then b=y1+(l-1)*strip d=b+strip a=x1 c=x2 end if GT=="hori" then a=x1+(l-1)*strip c=a+strip b=y1 d=y2 end return "clip("..a..","..b..","..c..","..d end) text2=text2:gsub("(.)("..ATAG..")",function(a,tblok) V1i=tblok:match("^{[^}]-"..tg.."([%d%-&][^\\}]*)") if V1i and tg:match("\\[fbs]") then VC=numgrad(V1i,V2,count,L,acc) end if V1i and tg:match("\\[%dac]") then VC=acgrad(V1i,V2,count,L,acc) end tblok=addtag3(tg..VC,tblok) return a..tblok end) end l2=line l2.text=text2:gsub(STAG,stags) :gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) if l==1 then text=l2.text else subs.insert(i+l-1,l2) end end if z<#sel then for s=z+1,#sel do sel[s]=sel[s]+total-1 end end for s=1,total-1 do table.insert(sel,i+s) end end

-- by character letrz=re.find(visible,".") if GBCn>#letrz then GO=nil end if GO and GT=="by c" then LTR={} TAG={} letrz=re.find(visible,".{"..GBCn.."}") rest=re.sub(visible,".{"..GBCn.."}","") for l=1,#letrz do table.insert(LTR,letrz[l].str) table.insert(TAG,"") end if rest~="" then table.insert(LTR,rest) table.insert(TAG,"") end for tg,V2 in tags:gmatch("(\\%d?%a+)([^\\]+)") do V1=text:match("^{[^}]-"..tg.."([%d%-&][^\\}]*)") or tag2style(tg,sr) if tg:match("fr") and res.short then V1=shortrot(V1) end initags=addtag3(tg..V1,initags) for l=2,#LTR do L=l count=#LTR half=math.ceil(#LTR/2) if gcl and gc1..gc2==visible then if l<=gcl then count=gcl else count=#LTR-gcl L=#LTR-l+1 end elseif res.middle then count=half if L>half then L=#LTR-L+1 end end if tg:match("\\[fbs]") then VC=numgrad(V1,V2,count,L,acc) end if tg:match("\\[%dac]") then VC=acgrad(V1,V2,count,L,acc) end TAG[l]=TAG[l]..tg..VC end end nt=LTR[1] for l=2,#LTR do nt=nt.."{"..TAG[l].."}"..LTR[l] end text=initags..textmod(orig,nt) text=text:gsub(ATAG,function(tg) return duplikill(tg) end) end

-- by line if GO and GT=="by l" then z1=z1+1 L=z1 total=GBL count=GBL half=math.ceil(total/2) if res.middle then count=half if L>half then L=total-L+1 end end stags=initags for tg,V2 in tags:gmatch("(\\%d?%a+)([^\\]+)") do V1=initags:match("^{[^}]-"..tg.."([%d%-&][^\\}]*)") or tag2style(tg,sr) if tg:match("fr") and res.short then V1=shortrot(V1) end if tg:match("\\[fbs]") then VC=numgrad(V1,V2,count,L,acc) end if tg:match("\\[%dac]") then VC=acgrad(V1,V2,count,L,acc) end stags=addtag3(tg..VC,stags) end text=text:gsub(STAG,stags) :gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) end

text=text:gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) line.text=text subs[i]=line end return sel end

-- SPECIAL FUNCTIONS -- function special(subs,sel) SF=res.spec if res.spec:match"transform" or res.spec:match"strikeout" then styleget(subs) getcolours() transphorm="" transphorm=gettags(transphorm) end if res.spec=="select overlaps" then sel=selover(subs) else for z=#sel,1,-1 do i=sel[z] progress(res.spec..": "..#sel-z.."/"..#sel) prog=math.floor((#sel-z+0.5)/#sel*100) aegisub.progress.set(prog) line=subs[i] text=line.text layer=line.layer linecheck() if GO then res.spec=SF else res.spec="nope" end text=text:gsub("\\1c","\\c")

if text:match("\\fscx") and text:match("\\fscy") then scalx=text:match("\\fscx([%d%.]+)") scaly=text:match("\\fscy([%d%.]+)") if res.spec=="fscx -> fscy" then text=text:gsub("(\\fscy)[%d%.]+","%1"..scalx) end if res.spec=="fscy -> fscx" then text=text:gsub("(\\fscx)[%d%.]+","%1"..scaly) end end

if res.spec=="move colour tag to first block" then tags=text:match(STAG) or "" text=text:gsub(STAG,"") klrs="" for klr in text:gmatch("\\[1234]?c&H%x+&") do klrs=klrs..klr end text=text:gsub("(\\[1234]?c&H%x+&)","") :gsub("{}","") text=tags.."{"..klrs.."}"..text text=tagmerge(text) :gsub(ATAG,function(tg) return duplikill(tg) end) end

if res.spec=="convert clip <-> iclip" then text=text:gsub("\\(i?)clip",function(k) if k=="" then return "\\iclip" else return "\\clip" end end) end

-- CLEAN UP TAGS if res.spec=="clean up tags" then text=text:gsub("{\\\\k0}","") :gsub("{(\\[^}]-)} *\\N *{(\\[^}]-)}","\\N{%1%2}") text=tagmerge(text) text=text:gsub("({\\[^}]-){(\\[^}]-})","%1%2") :gsub("{.-\\r","{\\r") :gsub("^{\\r([\\}])","{%1") text=text:gsub("\\fad%(0,0%)","") :gsub(ATAG.."$","") for tgs in text:gmatch(ATAG) do tgs2=tgs :gsub("\\([\\}])","%1") :gsub("(\\%a+)([%d%-]+%.%d+)",function(a,b) if not a:match("\\fn") then b=rnd2dec(b) end return a..b end) :gsub("(\\%a+)%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c) b=rnd2dec(b) c=rnd2dec(c) return a.."("..b..","..c..")" end) :gsub("(\\%a+)%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(a,b,c,d,e) b=rnd2dec(b) c=rnd2dec(c) d=rnd2dec(d) e=rnd2dec(e) return a.."("..b..","..c..","..d..","..e end) tgs2=duplikill(tgs2) tgs2=extrakill(tgs2) text=text:gsub(esc(tgs),tgs2) :gsub("^({\\[^}]-)\\frx0\\fry0","%1") end end

-- SORT TAGS if res.spec=="sort tags in set order" then text=text:gsub("\\a6","\\an8") :gsub("\\1c","\\c") -- run for each set of tags for tags in text:gmatch(ATAG) do orig=tags tags=tags:gsub("{.-\\r","{\\r") -- save & nuke transforms trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") ord="" -- go through tags, save them in order, and delete from tags for tg in order:gmatch("\\[%a%d]+") do tag=tags:match("("..tg.."[^\\}]-)[\\}]") if tg=="\\fs" then tag=tags:match("(\\fs%d[^\\}]-)[\\}]") end if tg=="\\fad" then tag=tags:match("(\\fad%([^\\}]-)[\\}]") end if tg=="\\c" then tag=tags:match("(\\c&[^\\}]-)[\\}]") end if tg=="\\i" then tag=tags:match("(\\i[^%a\\}]-)[\\}]") end if tg=="\\s" then tag=tags:match("(\\s[^%a\\}]-)[\\}]") end if tg=="\\p" then tag=tags:match("(\\p[^%a\\}]-)[\\}]") end if tag then ord=ord..tag etag=esc(tag) tags=tags:gsub(etag,"") end end -- attach whatever got left if tags~="{}" then ord=ord..tags:match("{(.-)}") end ordered="{"..ord..trnsfrm.."}" text=text:gsub(esc(orig),ordered) end end

-- CLIP TO DRAWING if res.spec=="convert clip to drawing" then if not text:match("\\clip") then ak() end text=text:gsub("^({\\[^}]-}).*","%1") text=text:gsub("^({[^}]*)\\clip%(m(.-)%)([^}]*)}","%1%3\\p1}m%2") if text:match("\\pos") then local xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") xx=round(xx) yy=round(yy) ctext=text:match("}m ([%d%a%s%-]+)") if not ctext then t_error("Vectorial clip missing.",1) end ctext2=ctext:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return a-xx.." "..b-yy end) ctext=ctext:gsub("%-","%%-") text=text:gsub(ctext,ctext2) end if not text:match("\\pos") then text=text:gsub("^{","{\\pos(0,0)") end if text:match("\\an") then text=text:gsub("\\an%d","\\an7") else text=text:gsub("^{","{\\an7") end if text:match("\\fscx") then text=text:gsub("(\\fscx)[%d%.]+","%1100") else text=text:gsub("\\p1","\\fscx100%1") end if text:match("\\fscy") then text=text:gsub("(\\fscy)[%d%.]+","%1100") else text=text:gsub("\\p1","\\fscy100%1") end end

-- DRAWING TO CLIP if res.spec=="convert drawing to clip" then if not text:match("\\p1") then ak() end text=text:gsub("^({[^}]*)\\p1([^}]-})(m [^{]*)","%1\\clip(%3)%2") scx=text:match("\\fscx([%d%.]+)") or 100 scy=text:match("\\fscy([%d%.]+)") or 100 if text:match("\\pos") then local xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") xx=round(xx) yy=round(yy) ctext=text:match("\\clip%(m ([^%)]+)%)") ctext2=ctext:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return round(a*scx/100+xx).." "..round(b*scy/100+yy) end) ctext=ctext:gsub("%-","%%-") text=text:gsub(ctext,ctext2) end if not text:match("\\pos") then text=text:gsub("^{","{\\pos(0,0)") end end

-- STRIKEOUT TO SELECTED if res.spec=="convert strikeout to selected" then ST1=transphorm ST2=transphorm:gsub("(\\%d?%a+)[^\\]+","%1") text=text:gsub("\\s1",ST1):gsub("\\s0",ST2) end

-- 3D SHADOW if res.spec=="create 3D effect from shadow" then xshad=text:match("^{[^}]-\\xshad([%d%.%-]+)") or 0 ax=math.abs(xshad) yshad=text:match("^{[^}]-\\yshad([%d%.%-]+)") or 0 ay=math.abs(yshad) if ax>ay then lay=math.floor(ax) else lay=math.floor(ay) end

text2=text:gsub("^({\\[^}]-)}","%1\\3a&HFF&}") :gsub("\\3a&H%x%x&([^}]-)(\\3a&H%x%x&)","%1%2")

for l=lay,1,-1 do line2=line f=l/lay txt=text2 if l==1 then txt=text end line2.text=txt :gsub("\\xshad([%d%.%-]+)",function(a) xx=tostring(f*a) xx=xx:gsub("([%d%-]+%.%d%d)%d+","%1") return "\\xshad"..xx end) :gsub("\\yshad([%d%.%-]+)",function(a) yy=tostring(f*a) yy=yy:gsub("([%d%-]+%.%d%d)%d+","%1") return "\\yshad"..yy end) line2.layer=layer+(lay-l) subs.insert(i+1,line2) end

if xshad~=0 and yshad~=0 then subs.delete(i) end end

-- CLIP GRID if res.spec=="chequerboard clip" then text=text:gsub("^({[^}]-)\\clip%([^%)]+%)","%1") :gsub("^({\\[^}]-)}","%1\\clip(m 100 100 l 140 100 l 140 180 l 180 180 l 180 140 l 100 140 m 180 100 l 220 100 l 220 180 l 260 180 l 260 140 l 180 140 m 260 100 l 300 100 l 300 180 l 340 180 l 340 140 l 260 140 m 340 100 l 380 100 l 380 180 l 420 180 l 420 140 l 340 140 m 420 100 l 460 100 l 460 180 l 500 180 l 500 140 l 420 140 m 500 100 l 540 100 l 540 180 l 580 180 l 580 140 l 500 140 m 580 100 l 620 100 l 620 180 l 660 180 l 660 140 l 580 140 m 660 100 l 700 100 l 700 180 l 740 180 l 740 140 l 660 140 m 740 100 l 780 100 l 780 180 l 820 180 l 820 140 l 740 140 m 820 100 l 860 100 l 860 180 l 900 180 l 900 140 l 820 140 m 900 100 l 940 100 l 940 180 l 980 180 l 980 140 l 900 140 m 980 100 l 1020 100 l 1020 180 l 1060 180 l 1060 140 l 980 140 m 100 180 l 140 180 l 140 260 l 180 260 l 180 220 l 100 220 m 180 180 l 220 180 l 220 260 l 260 260 l 260 220 l 180 220 m 260 180 l 300 180 l 300 260 l 340 260 l 340 220 l 260 220 m 340 180 l 380 180 l 380 260 l 420 260 l 420 220 l 340 220 m 420 180 l 460 180 l 460 260 l 500 260 l 500 220 l 420 220 m 500 180 l 540 180 l 540 260 l 580 260 l 580 220 l 500 220 m 580 180 l 620 180 l 620 260 l 660 260 l 660 220 l 580 220 m 660 180 l 700 180 l 700 260 l 740 260 l 740 220 l 660 220 m 740 180 l 780 180 l 780 260 l 820 260 l 820 220 l 740 220 m 820 180 l 860 180 l 860 260 l 900 260 l 900 220 l 820 220 m 900 180 l 940 180 l 940 260 l 980 260 l 980 220 l 900 220 m 980 180 l 1020 180 l 1020 260 l 1060 260 l 1060 220 l 980 220 m 100 260 l 140 260 l 140 340 l 180 340 l 180 300 l 100 300 m 180 260 l 220 260 l 220 340 l 260 340 l 260 300 l 180 300 m 260 260 l 300 260 l 300 340 l 340 340 l 340 300 l 260 300 m 340 260 l 380 260 l 380 340 l 420 340 l 420 300 l 340 300 m 420 260 l 460 260 l 460 340 l 500 340 l 500 300 l 420 300 m 500 260 l 540 260 l 540 340 l 580 340 l 580 300 l 500 300 m 580 260 l 620 260 l 620 340 l 660 340 l 660 300 l 580 300 m 660 260 l 700 260 l 700 340 l 740 340 l 740 300 l 660 300 m 740 260 l 780 260 l 780 340 l 820 340 l 820 300 l 740 300 m 820 260 l 860 260 l 860 340 l 900 340 l 900 300 l 820 300 m 900 260 l 940 260 l 940 340 l 980 340 l 980 300 l 900 300 m 980 260 l 1020 260 l 1020 340 l 1060 340 l 1060 300 l 980 300 m 100 340 l 140 340 l 140 420 l 180 420 l 180 380 l 100 380 m 180 340 l 220 340 l 220 420 l 260 420 l 260 380 l 180 380 m 260 340 l 300 340 l 300 420 l 340 420 l 340 380 l 260 380 m 340 340 l 380 340 l 380 420 l 420 420 l 420 380 l 340 380 m 420 340 l 460 340 l 460 420 l 500 420 l 500 380 l 420 380 m 500 340 l 540 340 l 540 420 l 580 420 l 580 380 l 500 380 m 580 340 l 620 340 l 620 420 l 660 420 l 660 380 l 580 380 m 660 340 l 700 340 l 700 420 l 740 420 l 740 380 l 660 380 m 740 340 l 780 340 l 780 420 l 820 420 l 820 380 l 740 380 m 820 340 l 860 340 l 860 420 l 900 420 l 900 380 l 820 380 m 900 340 l 940 340 l 940 420 l 980 420 l 980 380 l 900 380 m 980 340 l 1020 340 l 1020 420 l 1060 420 l 1060 380 l 980 380 m 100 420 l 140 420 l 140 500 l 180 500 l 180 460 l 100 460 m 180 420 l 220 420 l 220 500 l 260 500 l 260 460 l 180 460 m 260 420 l 300 420 l 300 500 l 340 500 l 340 460 l 260 460 m 340 420 l 380 420 l 380 500 l 420 500 l 420 460 l 340 460 m 420 420 l 460 420 l 460 500 l 500 500 l 500 460 l 420 460 m 500 420 l 540 420 l 540 500 l 580 500 l 580 460 l 500 460 m 580 420 l 620 420 l 620 500 l 660 500 l 660 460 l 580 460 m 660 420 l 700 420 l 700 500 l 740 500 l 740 460 l 660 460 m 740 420 l 780 420 l 780 500 l 820 500 l 820 460 l 740 460 m 820 420 l 860 420 l 860 500 l 900 500 l 900 460 l 820 460 m 900 420 l 940 420 l 940 500 l 980 500 l 980 460 l 900 460 m 980 420 l 1020 420 l 1020 500 l 1060 500 l 1060 460 l 980 460 m 100 500 l 140 500 l 140 580 l 180 580 l 180 540 l 100 540 m 180 500 l 220 500 l 220 580 l 260 580 l 260 540 l 180 540 m 260 500 l 300 500 l 300 580 l 340 580 l 340 540 l 260 540 m 340 500 l 380 500 l 380 580 l 420 580 l 420 540 l 340 540 m 420 500 l 460 500 l 460 580 l 500 580 l 500 540 l 420 540 m 500 500 l 540 500 l 540 580 l 580 580 l 580 540 l 500 540 m 580 500 l 620 500 l 620 580 l 660 580 l 660 540 l 580 540 m 660 500 l 700 500 l 700 580 l 740 580 l 740 540 l 660 540 m 740 500 l 780 500 l 780 580 l 820 580 l 820 540 l 740 540 m 820 500 l 860 500 l 860 580 l 900 580 l 900 540 l 820 540 m 900 500 l 940 500 l 940 580 l 980 580 l 980 540 l 900 540 m 980 500 l 1020 500 l 1020 580 l 1060 580 l 1060 540 l 980 540)}") end

-- BACK AND FORTH TRANSFORM if res.spec=="back and forth transform" and res.int>0 then if defaref and line.style=="Default" then sr=defaref else sr=stylechk(line.style) end -- clean up existing transforms if text:match("^{[^}]*\\t") then text=text:gsub(STAG,function(tg) return cleantr(tg) end) end startags=text:match(STAG) tags1="" for tg in transphorm:gmatch("\\[1234]?%a+") do val1=nil if not startags:match(tg.."[%d%-&%(]") then if tg=="\\clip" then val1="(0,0,1280,720)" else val1=tag2style(tg,sr) end if val1 then tags1=tags1..tg..val1 text=text:gsub("^({\\[^}]-)}","%1"..tg..val1.."}") end else val1=startags:match(tg.."([^\\}]+)") tags1=tags1..tg..val1 end end int=res.int tags2=transphorm dur=line.end_time-line.start_time count=math.ceil(dur/int) t=1 tin=0 tout=tin+int if not text:match("^{\\") then text="{\\}"..text end -- main function while t<=math.ceil(count/2) do text=text:gsub("^({\\[^}]*)}","%1\\t("..tin..","..tout..","..tags2..")}") if tin+int

-- SPLIT LINE IN 3 PARTS if res.spec=="split line in 3 parts" then start=line.start_time endt=line.end_time effect=line.effect -- line 3 line3=line line3.start_time=endt-res.trout line3.effect=effect.." pt.3" if line3.start_time~=line3.end_time then subs.insert(i+1,line3) end -- line 2 line2=line line2.start_time=start+res.trin line2.end_time=endt-res.trout line2.effect=effect.." pt.2" subs.insert(i+1,line2) -- line 1 line.start_time=start line.end_time=start+res.trin line.effect=effect.." pt.1" end

if res.spec~="create 3D effect from shadow" then line.text=text subs[i]=line if res.spec=="split line in 3 parts" and line.start_time==line.end_time then subs.delete(i) end end end end return sel end function selover(subs,sel) local dialogue={} for i,line in ipairs(subs) do if line.class=="dialogue" then line.i=i table.insert(dialogue,line) end end table.sort(dialogue,function(a,b) return a.start_time=end_time then end_time=line.end_time else table.insert(overlaps,line.i) end end sel=overlaps return sel end

-- reanimatools -- function round(num) num=math.floor(num+0.5) return num end function rnd2dec(num) num=math.floor((num*100)+0.5)/100 return num end function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") val="[%d%-&]" if not tgt then tgt=tg:match("(\\%d?%a+)%b()") val="%b()" end if not tgt then tgt=tg:match("\\fn") val="" end if not tgt then t_error("adding tag '"..tg.."' failed.") end if tgt:match("clip") then txt,r=txt:gsub("^({[^}]-)\\i?clip%b()","%1"..tg) if r==0 then txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end elseif no_tf:match("^({[^}]-)"..tgt..val) then txt=txt:gsub("^({[^}]-)"..tgt..val.."[^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end function tagmerge(text) repeat text,r=text:gsub("({\\[^}]-)}{(\\[^}]-})","%1%2") until r==0 return text end function tag2style(tg,sr) noneg="\\bord\\shad\\xbord\\ybord\\fs\\blur\\be\\fscx\\fscy" val=0 if tg=="\\fs" then val=sr.fontsize end if tg=="\\fsp" then val=sr.spacing end if tg=="\\fscx" then val=sr.scale_x end if tg=="\\fscy" then val=sr.scale_y end if tg:match"\\[xy]?bord" then val=sr.outline end if tg:match"\\[xy]?shad" then val=sr.shadow end if tg=="\\frz" then val=sr.angle end if val<0 and noneg:match(tg) then val=0 end if tg=="\\c" then val=sr.color1:gsub("H%x%x","H") end if tg=="\\2c" then val=sr.color2:gsub("H%x%x","H") end if tg=="\\3c" then val=sr.color3:gsub("H%x%x","H") end if tg=="\\4c" then val=sr.color4:gsub("H%x%x","H") end if tg=="\\1a" then val=sr.color1:match("H%x%x") end if tg=="\\2a" then val=sr.color2:match("H%x%x") end if tg=="\\3a" then val=sr.color3:match("H%x%x") end if tg=="\\4a" then val=sr.color4:match("H%x%x") end if tg=="\\alpha" then val="&H00&" end return val end function numgrad(V1,V2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc VC=rnd2dec(acc_fac*(V2-V1)+V1) return VC end function acgrad(C1,C2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc B1,G1,R1=C1:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=C2:match("(%x%x)(%x%x)(%x%x)") A1=C1:match("(%x%x)") R1=R1 or A1 A2=C2:match("(%x%x)") R2=R2 or A2 nR1=(tonumber(R1,16)) nR2=(tonumber(R2,16)) R=acc_fac*(nR2-nR1)+nR1 R=tohex(round(R)) CC="&H"..R.."&" if B1 then nG1=(tonumber(G1,16)) nG2=(tonumber(G2,16)) nB1=(tonumber(B1,16)) nB2=(tonumber(B2,16)) G=acc_fac*(nG2-nG1)+nG1 B=acc_fac*(nB2-nB1)+nB1 G=tohex(round(G)) B=tohex(round(B)) CC="&H"..B..G..R.."&" end return CC end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function shortrot(v) if tonumber(v)>180 then v=v-360 end return v end function trem(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") return tags end function cleantr(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..trnsfrm.."}") return tags end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function textmod(orig,text) if text=="" then return orig end tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 vis=text:gsub("%b{}","") ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end stags=text:match(STAG) or "" text=text:gsub(STAG,"") :gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n,t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext=stags..newline text=newtext:gsub("{}","") return text end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function msgbox(message,h,w) pres,rez=ADD({{width=w or 24,height=h or 5,name="msg",class="textbox",value=message}},{"OK","clip bored"},{close='OK'}) if pres=="clip bored" then clipboard.set(rez.msg) end end function styleget(subs) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i]) end if subs[i].class=="dialogue" then break end end end function stylechk(sn) for i=1,#styles do if sn==styles[i].name then sr=styles[i] if sr.name=="Default" then defaref=styles[i] end break end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function selcheck() SC=0 for k,v in ipairs(hh_gui) do if v.class=="checkbox" and v.y<10 and res[v.name] then SC=1 end if v.name=="reuse" and res.reuse then SC=1 end if v.name=="moretags" and res.moretags:len()>1 then SC=1 end end if SC==0 then t_error("No tags selected",1) end end

-- Config Stuff -- function saveconfig() hydraconf="Hydra 4+ Config\n\n" for key,v in ipairs(hh_gui) do if v.class:match"edit" or v.class=="dropdown" or v.class=="coloralpha" then if v.name~="linetext" and v.name~="herp" and not v.name:match"app" then hydraconf=hydraconf..v.name..":"..res[v.name].."\n" end end if v.class=="checkbox" and v.name~="save" then hydraconf=hydraconf..v.name..":"..tf(res[v.name]).."\n" end end file=io.open(hydrakonfig,"w") file:write(hydraconf) file:close() ADD({{class="label",label="Config saved to:\n"..hydrakonfig}},{"OK"},{close='OK'}) end function loadconfig() file=io.open(hydrakonfig) if file~=nil then konf=file:read("*all") sm=tonumber(konf:match("smode:(.-)\n")) io.close(file) for k,v in ipairs(hh1) do if v.class:match"edit" or v.class=="checkbox" or v.class=="coloralpha" then if konf:match(v.name) then v.value=detf(konf:match(v.name..":(.-)\n")) end end end for k,v in ipairs(hh2) do if v.class:match"edit" or v.class=="checkbox" or v.class=="dropdown" then if konf:match(v.name) then v.value=detf(konf:match(v.name..":(.-)\n")) end end end for k,v in ipairs(hh3) do if v.class:match"edit" or v.class=="checkbox" or v.class=="dropdown" then if konf:match(v.name) then v.value=detf(konf:match(v.name..":(.-)\n")) end if hydralast then if v.name=="gtype" and res.gtype then v.value=hydralast.gtype end if v.name=="accel" and res.accel then v.value=hydralast.accel end if v.name=="stripe" and res.stripe then v.value=hydralast.stripe end if v.name=="middle" and res.middle then v.value=hydralast.middle end end end end else sm=1 end end hydraulics={"A multi-headed typesetting tool","Nine heads typeset better than one.","Eliminating the typing part of typesetting","Mass-production of typesetting tags","Hydraulic typesetting machinery","Making sure your subtitles aren't dehydrated","Making typesetting so easy that even you can do it!","A monstrous typesetting tool","A deadly typesetting beast","Building monstrous scripts with ease","For irrational typesetting wizardry","Building a Wall of Tags","The Checkbox Onslaught","HYperactively DRAstic","HYperdimensional DRAma","I can has moar tagz? ","Transforming the subtitle landscape"} function hydra(subs,sel) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" COMM="{[^\\}]-}" hydrakonfig=ADP("?user").."\\hydra4.conf" app_lay={"All Layers"} app_sty={"All Styles"} app_act={"All Actors"} app_eff={"All Effects"} for z,i in ipairs(sel) do L=subs[i] layr=L.layer stl=L.style akt=L.actor eph=L.effect asdf=0 for a=1,#app_lay do if layr==app_lay[a] then asdf=1 end end if asdf==0 then table.insert(app_lay,layr) end asdf=0 for a=1,#app_sty do if stl==app_sty[a] then asdf=1 end end if asdf==0 then table.insert(app_sty,stl) end asdf=0 for a=1,#app_act do if akt==app_act[a] then asdf=1 end end if asdf==0 then table.insert(app_act,akt) end asdf=0 for a=1,#app_eff do if eph==app_eff[a] then asdf=1 end end if asdf==0 then table.insert(app_eff,eph) end end hr=math.random(1,#hydraulics) if #sel==0 then t_error("No selection",1) end oneline=subs[sel[1]] linetext=oneline.text:gsub("%b{}","") alfas={"00","10","20","30","40","50","60","70","80","90","A0","B0","C0","D0","E0","F0","F8","FF"} hh1={ {x=0,y=0,width=5,class="label",label="Hydra "..script_version.." - "..hydraulics[hr]}, {x=6,y=0,width=2,class="label",name="info",label="Selected lines: "..#sel},

{x=0,y=1,class="checkbox",name="k1",label="Primary:"}, {x=1,y=1,class="coloralpha",name="c1"}, {x=0,y=2,class="checkbox",name="k3",label="Border:"}, {x=1,y=2,class="coloralpha",name="c3"}, {x=0,y=3,class="checkbox",name="k4",label="Shadow:"}, {x=1,y=3,class="coloralpha",name="c4"}, {x=0,y=4,class="checkbox",name="k2",label="useless... (2c):"}, {x=1,y=4,class="coloralpha",name="c2"}, {x=0,y=5,class="checkbox",name="alfas",label="Include alphas", hint="Include alphas from colours pickers.\nRequires Aegisub r7993 or higher."}, {x=1,y=5,class="checkbox",name="aonly",label="only",hint="Use only alphas, not colours"}, {x=0,y=6,class="checkbox",name="italix",label="Italics"}, {x=1,y=6,class="checkbox",name="bolt",label="Bold"}, {x=0,y=7,class="checkbox",name="under",label="Underline"}, {x=1,y=7,class="checkbox",name="strike",label="Strike"},

{x=2,y=1,class="checkbox",name="bord1",label="\\bord"}, {x=3,y=1,width=2,class="floatedit",name="bord2",value=0}, {x=2,y=2,class="checkbox",name="shad1",label="\\shad"}, {x=3,y=2,width=2,class="floatedit",name="shad2",value=0}, {x=2,y=3,class="checkbox",name="fs1",label="\\fs"}, {x=3,y=3,width=2,class="floatedit",name="fs2",value=50}, {x=2,y=4,class="checkbox",name="spac1",label="\\fsp"}, {x=3,y=4,width=2,class="floatedit",name="spac2",value=1}, {x=2,y=5,class="checkbox",name="blur1",label="\\blur"}, {x=3,y=5,width=2,class="floatedit",name="blur2",value=0.5}, {x=2,y=6,class="checkbox",name="be1",label="\\be"}, {x=3,y=6,width=2,class="floatedit",name="be2",value=1}, {x=2,y=7,class="checkbox",name="fscx1",label="\\fscx"}, {x=3,y=7,width=2,class="floatedit",name="fscx2",value=100}, {x=2,y=8,class="checkbox",name="fscy1",label="\\fscy"}, {x=3,y=8,width=2,class="floatedit",name="fscy2",value=100},

{x=5,y=1,class="checkbox",name="xbord1",label="\\xbord"}, {x=6,y=1,width=2,class="floatedit",name="xbord2"}, {x=5,y=2,class="checkbox",name="ybord1",label="\\ybord"}, {x=6,y=2,width=2,class="floatedit",name="ybord2"}, {x=5,y=3,class="checkbox",name="xshad1",label="\\xshad"}, {x=6,y=3,width=2,class="floatedit",name="xshad2"}, {x=5,y=4,class="checkbox",name="yshad1",label="\\yshad"}, {x=6,y=4,width=2,class="floatedit",name="yshad2"}, {x=5,y=5,class="checkbox",name="fax1",label="\\fax"}, {x=6,y=5,width=2,class="floatedit",name="fax2",value=0.05}, {x=5,y=6,class="checkbox",name="fay1",label="\\fay"}, {x=6,y=6,width=2,class="floatedit",name="fay2",value=0.05}, {x=5,y=7,class="checkbox",name="frx1",label="\\frx"}, {x=6,y=7,width=2,class="floatedit",name="frx2"}, {x=5,y=8,class="checkbox",name="fry1",label="\\fry"}, {x=6,y=8,width=2,class="floatedit",name="fry2"}, {x=5,y=9,class="checkbox",name="frz1",label="\\frz"}, {x=6,y=9,width=2,class="floatedit",name="frz2"},

{x=1,y=8,class="checkbox",name="q2",label="\\q2"}, {x=0,y=8,class="checkbox",name="glo",label="Global fade",hint="global fade - IN on first line, OUT on last line"}, {x=0,y=9,class="checkbox",name="fade",label="\\fad (in,out)"}, {x=1,y=9,width=2,class="floatedit",name="fadin",min=0,hint="fade in"}, {x=3,y=9,width=2,class="floatedit",name="fadout",min=0,hint="fade out"}, } hh2={ {x=8,y=0,class="label",name="startmode",label="Start mode"}, {x=9,y=0,class="dropdown",name="smode",items={"1","2","3"},value="1"}, {x=8,y=1,class="checkbox",name="layer",label="layer"}, {x=9,y=1,class="dropdown",name="layers",items={"-5","-4","-3","-2","- 1","+1","+2","+3","+4","+5"},value="+1"}, {x=8,y=2,class="checkbox",name="arfa",label="\\alpha"}, {x=9,y=2,class="dropdown",name="alpha",items=alfas,value="00"}, {x=8,y=3,class="checkbox",name="arf1",label="\\1a"}, {x=9,y=3,class="dropdown",name="alph1",items=alfas,value="00"}, {x=8,y=4,class="checkbox",name="arf2",label="\\2a"}, {x=9,y=4,class="dropdown",name="alph2",items=alfas,value="00"}, {x=8,y=5,class="checkbox",name="arf3",label="\\3a"}, {x=9,y=5,class="dropdown",name="alph3",items=alfas,value="00"}, {x=8,y=6,class="checkbox",name="arf4",label="\\4a"}, {x=9,y=6,class="dropdown",name="alph4",items=alfas,value="00"}, {x=8,y=7,class="checkbox",name="an1",label="\\an"}, {x=9,y=7,class="dropdown",name="an2",items={"1","2","3","4","5","6","7","8","9"},value="5"},

{x=8,y=8,width=2,class="label",label="Add with each line:"}, {x=8,y=9,width=2,class="floatedit",name="add",hint="works with regular tags,\ni.e. the middle two columns"}, {x=0,y=10,class="label",label="Additional tags:"}, {x=1,y=10,width=5,class="edit",name="moretags",value="\\"}, {x=6,y=10,class="checkbox",name="show",label="show ",hint="Show the tags being applied.\nNote: only transformable tags."}, {x=7,y=10,class="checkbox",name="reuse",label="reuse ",hint="Reuse the (transformable) tags from last run.\nCan be used with a different button\nor 'apply to' restriction."}, } hh3={ {x=0,y=11,class="label",label="Tag position*:"}, {x=1,y=11,width=5,class="edit",name="linetext",value=linetext,hint="Place an asterisk where you want the tags."}, {x=6,y=11,width=2,class="dropdown",name="tagpres",items={"--- presets ---","before last char.","in the middle","1/4 of text","3/4 of text","1/8 of text","3/8 of text","5/8 of text","7/8 of text","custom pattern","section","every char.","every word","text position"},value="--- presets ---",hint="presets/options for tag position"},

{x=0,y=12,class="label",label="Transform t1,t2:"}, {x=1,y=12,width=2,class="floatedit",name="trin"}, {x=3,y=12,width=2,class="floatedit",name="trout"}, {x=5,y=12,width=3,class="checkbox",name="tend",label="Count times from end"},

{x=0,y=13,class="label",label="Transform mode:"}, {x=1,y=13,width=2,class="dropdown",name="tmode",items={"normal","add2first","add2all","all tag blocks"},value="normal",hint="new \\t | add to first \\t | add to all \\t | add to all {\\tag blocks}"}, {x=3,y=13,width=2,class="checkbox",name="relative",label="Relative transform", hint="Example:\ntag: \\frz30\ninput: 60\nresult: \\t(\\frz90)"}, {x=5,y=13,class="label",label=" Accel:"},

{x=0,y=14,class="label",label="Shift times/interval:"}, {x=1,y=14,width=2,class="floatedit",name="int",value=0,hint="'normal' transform: shift \\t times each line\n'all tag blocks': shift \\t times each block\n'back and forth transform': interval"}, {x=6,y=13,width=2,class="floatedit",name="accel",value=1,min=0,hint="<1 starts fast, >1 starts slow"},

{x=3,y=14,class="label",label="Special functions:"}, {x=4,y=14,width=4,class="dropdown",name="spec",items={"fscx -> fscy","fscy -> fscx","move colour tag to first block","convert clip <-> iclip","clean up tags","sort tags in set order","back and forth transform","select overlaps","convert clip to drawing","convert drawing to clip","convert strikeout to selected","chequerboard clip","create 3D effect from shadow","split line in 3 parts"},value="convert clip <-> iclip"},

{x=0,y=15,class="label",label="Gradient:"}, {x=1,y=15,width=2,class="dropdown",name="gtype",items={"vertical","horizontal","by character","by line"},value="by character", hint="gradient from current values to selected values\nvertical/horizontal requires clip\nworks with accel"}, {x=3,y=15,width=2,class="floatedit",name="stripe",value=2,min=0,hint="width of clip stripes for vertical/horizontal gradient"}, {x=5,y=15,class="label",label="pxl/stripe"}, {x=6,y=15,width=2,class="checkbox",name="short",label="Shorter rotations",value=true,hint="rotate in shorter direction"}, {x=8,y=15,width=2,class="checkbox",name="middle",label="Centered gradient", hint="selected tags will be in the middle;\nend will be same as beginning"},

{x=8,y=10,width=2,class="label",label="Apply to:"}, {x=8,y=11,width=2,class="dropdown",name="applay",items=app_lay,value="All Layers"}, {x=8,y=12,width=2,class="dropdown",name="applst",items=app_sty,value="All Styles"}, {x=8,y=13,width=2,class="dropdown",name="applac",items=app_act,value="All Actors"}, {x=8,y=14,width=2,class="dropdown",name="applef",items=app_eff,value="All Effects"}, } buttons={{"Apply","Repeat Last","Load Medium","Load Full","Cancel"}, {"Apply","Repeat Last","Load Full","Cancel"},{"Apply","Transform","Gradient","Repeat Last","Special","Save Config","Help","Cancel"}} loadconfig() heads=sm*2+1 hh_gui=hh1 loaded=sm progress(string.format("Loading Hydra Heads 1-"..heads)) if sm==2 then for i=1,#hh2 do l=hh2[i] table.insert(hh_gui,l) end loaded=2 end if sm==3 then for i=1,#hh2 do l=hh2[i] table.insert(hh_gui,l) end for i=1,#hh3 do l=hh3[i] table.insert(hh_gui,l) end end hh_buttons=buttons[sm] P,res=ADD(hh_gui,hh_buttons,{ok='Apply',cancel='Cancel'})

if P=="Load Medium" then progress(string.format("Loading Heads 4-5")) for key,val in ipairs(hh_gui) do val.value=res[val.name] end for i=1,#hh2 do l=hh2[i] table.insert(hh_gui,l) end loaded=2 P,res=ADD(hh_gui,buttons[2],{ok='Apply',cancel='Cancel'}) end

if P=="Load Full" then progress(string.format("Loading Heads "..(loaded+1)*2 .."-7")) for key,val in ipairs(hh_gui) do val.value=res[val.name] end if loaded<2 then for i=1,#hh2 do l=hh2[i] table.insert(hh_gui,l) end end for i=1,#hh3 do l=hh3[i] table.insert(hh_gui,l) end loaded=3 P,res=ADD(hh_gui,buttons[3],{ok='Apply',cancel='Cancel'}) end

if P=="Help" then progress(string.format("Loading Head 8")) for key,val in ipairs(hh_gui) do val.value=res[val.name] end hhh={x=0,y=16,width=10,class="dropdown",name="herp",items={"HELP (scroll/click to read)", "Standard mode: check tags, set values, click on 'Apply'.", "Transform mode normal: check tags, set values, set t1/t2/accel if needed, click on 'Transform'.", "Transform mode add2first: the transforms will be added to the first existing transform in the line.", "Transform mode add2all: the transforms will be added to all existing transforms in the line.", "Transform mode 'all tag blocks': the transforms will be added to all tag blocks, whether they have transforms or not.", "Shift times/interval: 'normal' tf - shift \\t times each line; 'all tag blocks' - shift \\t times each tag block.", "Relative transform: If you have frz30 and set transform to frz60, you get \\t(\\frz90), or transform BY 60.", "Relative transform: This allows you to keep layers with different frz in sync. (No effect on alpha/colours.)", "Additional tags: type any extra tags you want to add.", "Add with each line: '2' means that for each line, the value of all checked applicable tags is increased by an extra 2.", "Tag position: This shows the text of your first line. Type * where you want your tags to go.", "Tag position presets: This places tags in specified positions, proportionally for each selected line.", "Gradient: Current state is the start. Given tags are the end. Accel works with this. Vertical/horizontal need a clip.", "Centered gradient: 'There and back.' Given state will be in the middle. Last line/character will be the same as the first.", "Special functions: select a function, click on 'Special'.", "Special functions - back and forth transform: select tags, set 'interval'. Missing initial tags are taken from style.", "Special functions - create 3D effect from shadow: creates a 3D effect using layers. Requires xshad/yshad.", "Special functions - split line in 3 parts: uses t1 and t2 as time markers.", },value="HELP (scroll/click to read)"} table.insert(hh_gui,hhh) P,res=ADD(hh_gui,{"Apply","Transform","Gradient","Repeat Last","Special","Save Config","Cancel"},{ok='Apply',cancel='Cancel'}) end

if res.tmode=="normal" then tmode=1 end if res.tmode=="add2first" then tmode=2 end if res.tmode=="add2all" then tmode=3 end if res.tmode=="all tag blocks" then tmode=4 end if res.tagpres=="in the middle" then fak=0.5 end if loaded==3 and res.tagpres:match("of text") then fa,fb=res.tagpres:match("(%d)/(%d)") fak=fa/fb end if res.aonly then res.alfas=true end

if P~="Repeat Last" then hydralast=res Plast=P end if P=="Apply" then selcheck() trans=0 hh9(subs,sel) end if P=="Transform" then selcheck() trans=1 hh9(subs,sel) end if P=="Gradient" then selcheck() hydradient(subs,sel) end if P=="Special" then sel=special(subs,sel) end if P=="Save Config" then saveconfig() end if P=="Repeat Last" then res=hydralast if Plast=="Gradient" then hydradient(subs,sel) elseif Plast=="Special" then sel=special(subs,sel) else hh9(subs,sel) end end aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(hydra) else aegisub.register_macro(script_name,script_description,hydra) end -- Hyperdimensional Relocator offers a plethora of functions, focusing primarily on \pos, \move, \org, \clip, and rotations. -- Check Help (Space Travel Guide) for detailed description of all functions. script_name="Hyperdimensional Relocator" script_description="Advanced metamorphosis of multidimensional coordinates." script_author="reanimated" script_url="http://unanimated.xtreemhost.com/ts/relocator.lua" script_version="4.0" script_namespace="ua.Relocator" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="4.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' function positron(subs,sel) ps=res.post shake={} shaker={} count=0 nsel={} for z,i in ipairs(sel) do table.insert(nsel,i) end if res.posi:match("fbf X") then XYtab={} for z,i in ipairs(sel) do line=subs[i] text=line.text local X,Y=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") table.insert(XYtab,{x=X,y=Y}) end if res.first then Xref=XYtab[1].x Yref=XYtab[1].y else Xref=XYtab[#XYtab].x Yref=XYtab[#XYtab].y end end

-- Replicate GUI if res.posi=="replicate" then rplGUI={ {x=0,y=0,width=2,class="label",label="Replicas:"}, {x=2,y=0,width=3,class="intedit",name="rep",value=repl or 3,min=1,hint="replicas to create"}, {x=0,y=1,width=3,class="label",label="Distances for:"}, {x=3,y=1,width=1,class="dropdown",name="dtype",items={"one replica","last replica"},value="last replica"}, {x=4,y=1,width=2,class="label",label=" last replica"}, {x=6,y=1,width=2,class="label",label="formation curve"}, {x=0,y=2,width=2,class="label",label="X Distance:"}, {x=0,y=3,width=2,class="label",label="Y Distance:"}, {x=2,y=2,width=2,class="floatedit",name="xdist",value=xdist or 0}, {x=2,y=3,width=2,class="floatedit",name="ydist",value=ydist or 0}, {x=4,y=2,width=2,class="dropdown",name="xar",items={"absolute","relative"},value="relative",hint="distance type for 'last replica'"}, {x=4,y=3,width=2,class="dropdown",name="yar",items={"absolute","relative"},value="relative",hint="distance type for 'last replica'"}, {x=6,y=2,width=2,class="floatedit",name="xcel",value=xcel or 1,min=0,hint="acceleration for 'last replica'"}, {x=6,y=3,width=2,class="floatedit",name="ycel",value=ycel or 1,min=0,hint="acceleration for 'last replica'"}, {x=0,y=4,width=6,class="checkbox",name="mov",label="use \\move for last replica coordinates"}, {x=0,y=5,width=2,class="checkbox",name="delay",label="Delay:"}, {x=2,y=5,width=3,class="intedit",name="del",value=0,min=0}, {x=5,y=5,width=1,class="label",label="frames"}, {x=6,y=5,width=2,class="checkbox",name="keepend",label="keep end",hint="try to keep end time\nsame as original if possible"}, } lucid={x=0,y=6,width=8,height=8,class="textbox",value=replika} repeat if reprez then for k,v in ipairs(rplGUI) do if v.name then v.value=reprez[v.name] end end end btns={"Replicate","Elucidate","Disintegrate"} if pres=="Elucidate" then table.insert(rplGUI,lucid) table.remove(btns,2) end pres,rez=ADD(rplGUI,btns,{ok='Replicate',close='Disintegrate'}) reprez=rez until pres~="Elucidate" if pres=="Disintegrate" then ak() end repl=rez.rep+1 xcel=rez.xcel if xcel==0 then xcel=1 end ycel=rez.ycel if ycel==0 then ycel=1 end if rez.mov then moverep=true else moverep=false end if rez.dtype=="one replica" and not moverep then dtype=1 xcel=1 ycel=1 else dtype=0 end if rez.xar=="absolute" then xabs=true else xabs=false end if rez.yar=="absolute" then yabs=true else yabs=false end xdist=rez.xdist ydist=rez.ydist if rez.delay then replay=rez.del else replay=0 end if rez.keepend then endrep=true else endrep=false end end

-- FBF Retrack Data Gathering if res.posi=="fbf retrack" then if #sel<3 then t_error("Error: You must select at least 3 lines for 'fbf retrack'.",1) end retrack={} truck="" posref={} posx1,posy1=subs[sel[1]].text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if not posx1 then t_error("Error: Missing \\pos in the first line.",1) end posxl,posyl=subs[sel[#sel]].text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if not posxl then t_error("Error: Missing \\pos in the last line.",1) end -- retrack tab for z,i in ipairs(sel) do l=subs[i] fr1=ms2fr(l.start_time) if not truck:match("|"..fr1.."|") then table.insert(retrack,fr1) truck=truck.."|"..fr1.."|" end end table.sort(retrack) -- posref tab if res.smo then for z,i in ipairs(sel) do l=subs[i] frame=ms2fr(l.start_time) fpos,total=detrack(z,sel,retrack,frame) posix,posiy=l.text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if not posix then t_error("Error: Missing \\pos in line "..z..".",1) end if not posref[fpos] then posref[fpos]={x=posix,y=posiy} end end end if res.layers and #retrack==1 then t_error("Error: All lines start on the same frame.\nIf you want to change position of signs\non the same frame, uncheck layers.",1) end if res.layers then total=#retrack else total=#sel end xstep=round((posxl-posx1)/(total-1),2) ystep=round((posyl-posy1)/(total-1),2) if ps<=0 then ps=1 end if res.eks>0 then acx=res.eks else acx=ps end if res.wai>0 then acx=res.wai else acy=ps end end

-- Positron Cannon Lines -- if res.posi=="space out letters" then table.sort(sel,function(a,b) return a>b end) end for z,i in ipairs(sel) do progress("Depositing line: "..z.."/"..#sel) line=subs[i] text=line.text nontra=text:gsub("\\t%b()","")

-- Align X if res.posi=="Align X" then if z==1 and not text:match("\\pos") then t_error("Missing \\pos tag.",1) end if z==1 and res.first then pxx=text:match("\\pos%(([%d%.%-]+),") ps=pxx end text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)","\\pos("..ps..",%2)") end

-- Align Y if res.posi=="Align Y" then if z==1 and not text:match("\\pos") then t_error("Missing \\pos tag.",1) end if z==1 and res.first then pyy=text:match("\\pos%([%d%.%-]+,([%d%.%-]+)") ps=pyy end text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)","\\pos(%1,"..ps..")") end

-- Mirrors if res.posi:match"mirror" then if not text:match("\\pos") and not text:match("\\move") then t_error("Fail. Some lines are missing \\pos.",1) end info(subs) if not text:match("^{[^}]-\\an%d") then sr=stylechk(subs,line.style) text=text:gsub("^","{\\an"..sr.align.."}") :gsub("({\\an%d)}{\\","%1\\") end if ps and ps~=0 then resx=2*ps resy=2*ps end if res.posi=="horizontal mirror" then mirs={"1","4","7","9","6","3"} text2=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(x,y) return "\\pos("..resx-x..","..y..")" end) :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(x,y,x2,y2) return "\\move("..resx-x..","..y..","..resx-x2..","..y2 end) :gsub("\\an([147369])",function(a) for m=1,6 do if a==mirs[m] then b=mirs[7-m] end end return "\\an"..b end) if res.rota then if not text2:match("^{[^}]-\\fry") then text2=addtag("\\fry0",text2) end text2=flip("fry",text2) end else mirs={"1","2","3","9","8","7"} text2=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(x,y) return "\\pos("..x..","..resy-y..")" end) :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(x,y,x2,y2) return "\\move("..x..","..resy-y..","..x2..","..resy-y2 end) :gsub("\\an([123789])",function(a) for m=1,6 do if a==mirs[m] then b=mirs[7-m] end end return "\\an"..b end) if res.rota then if not text2:match("^{[^}]-\\frx") then text2=addtag("\\frx0",text2) end text2=flip("frx",text2) end end l2=line l2.text=text2 if res.keep then subs.insert(i+1,l2) for i=z,#sel do sel[i]=sel[i]+1 end else text=text2 end end

-- org to fax if res.posi=="org to fax" then if text:match("\\move") then t_error("What's \\move doing there??",1) end if not text:match("\\pos") then text=getpos(subs,text) end if not text:match("\\org") then t_error("Missing \\org.",1) end pox,poy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)") orx,ory=text:match("\\org%(([%d%.%-]+),([%d%.%-]+)") sr=stylechk(subs,line.style) nontra=text:gsub("\\t%b()","") rota=nontra:match("\\frz([%d%.%-]+)") or sr.angle scx=nontra:match("\\fscx([%d%.]+)") or sr.scale_x scy=nontra:match("\\fscy([%d%.]+)") or sr.scale_y scr=scx/scy ad=pox-orx op=poy-ory tang=(ad/op) ang1=math.deg(math.atan(tang)) ang2=ang1-rota tangf=math.tan(math.rad(ang2))

faks=round(tangf/scr,2) text=addtag3("\\fax"..faks,text) text=text:gsub("\\org%([^%)]+%)","") :gsub(ATAG,function(tg) return duplikill(tg) end) end

-- clip to fax if res.posi=="clip to fax" then if not text:match("\\clip%(m") then t_error("Missing \\clip.",1) end cx1,cy1,cx2,cy2,cx3,cy3,cx4,cy4=text:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+)") if cx1==nil then cx1,cy1,cx2,cy2=text:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+)") end sr=stylechk(subs,line.style) nontra=text:gsub("\\t%b()","") rota=nontra:match("\\frz([%d%.%-]+)") or sr.angle scx=nontra:match("\\fscx([%d%.]+)") or sr.scale_x scy=nontra:match("\\fscy([%d%.]+)") or sr.scale_y scr=scx/scy ad=cx1-cx2 op=cy1-cy2 tang=(ad/op) ang1=math.deg(math.atan(tang)) ang2=ang1-rota tangf=math.tan(math.rad(ang2))

faks=round(tangf/scr,2) text=addtag3("\\fax"..faks,text) if cy4 then tang2=((cx3-cx4)/(cy3-cy4)) ang3=math.deg(math.atan(tang2)) ang4=ang3-rota tangf2=math.tan(math.rad(ang4)) faks2=round(tangf2,2) endcom="" repeat text=text:gsub("({[^}]-})%s*$",function(ec) endcom=ec..endcom return "" end) until not text:match("}$") text=text:gsub("(.)$","{\\fax"..faks2.."}%1")

vis=text:gsub("%b{}","") orig=text:gsub(STAG,"") tg=text:match(STAG) chars={} for char in vis:gmatch(".") do table.insert(chars,char) end faxdiff=(faks2-faks)/(#chars-1) tt=chars[1] for c=2,#chars do if c==#chars then ast="" else ast="*" end if chars[c]==" " then tt=tt.." " else tt=tt.."{"..ast.."\\fax"..round((faks+faxdiff*(c-1)),2) .."}"..chars[c] end end text=tg..tt if orig:match("{%*?\\") then text=textmod(orig,text) end

text=text..endcom end text=text:gsub("\\clip%b()","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub(ATAG,function(tg) return duplikill(tg) end) :gsub("%**}","}") end

-- clip to frz if res.posi=="clip to frz" then if not text:match("\\clip%(m") then t_error("Missing \\clip.",1) end cx1,cy1,cx2,cy2,cx3,cy3,cx4,cy4=text:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+)") if cx1==nil then cx1,cy1,cx2,cy2=text:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+)") end ad=cx2-cx1 op=cy1-cy2 tang=(op/ad) ang1=math.deg(math.atan(tang)) rota=round(ang1,2) if ad<0 then rota=rota-180 end

if cy4 then ad2=cx4-cx3 op2=cy3-cy4 tang2=(op2/ad2) ang2=math.deg(math.atan(tang2)) rota2=round(ang2,2) if ad2<0 then rota2=rota2-180 end else rota2=rota end rota3=(rota+rota2)/2

text=addtag("\\frz"..rota3,text) text=text:gsub("\\clip%b()","") :gsub(ATAG,function(tg) return duplikill(tg) end) end

-- clip to reposition if res.posi=="clip to reposition" then if not text:match("\\clip%(m") then t_error("Missing \\clip.",1) end cx1,cy1,cx2,cy2=text:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+)") repo1=cx2-cx1 repo2=cy2-cy1 text=text:gsub("\\clip%b()","") :gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)",function(x,y) return "\\pos("..round(x+repo1,2)..","..round(y+repo2,2) end) end

-- shake if res.posi=="shake" then if text:match("\\move") then t_error("What's \\move doing there??",1) end if not text:match("\\pos") then text=getpos(subs,text) end s=line.start_time diam=res.post scal=res.force if diam==0 and not res.sca then diamx=res.eks diamy=res.wai else diamx=diam diamy=diam end shx=math.random(-100,100)/100*diamx if res.smo and lshx then shx=(shx+3*lshx)/4 end shy=math.random(-100,100)/100*diamy if res.smo and lshy then shy=(shy+3*lshy)/4 end shr=math.random(-100,100)/100*diam if res.smo and lshr then shr=(shr+3*lshr)/4 end shsx=math.random(-100,100)/100*scal if res.smo and lshsx then shsx=(shsx+3*lshsx)/4 end shsy=math.random(-100,100)/100*scal if res.smo and lshsy then shsy=(shsy+3*lshsy)/4 end if res.layers then ch=0 for p=1,#shake do sv=shake[p] if sv[1]==s then ch=1 shx=sv[2] shy=sv[3] shr=sv[4] shsx=sv[5] shsy=sv[6] end end if ch==0 then a={s,shx,shy,shr,shsx,shsy} table.insert(shake,a) end end lshx=shx lshy=shy lshr=shr lshsx=shsx lshsy=shsy text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(x,y) return "\\pos("..x+shx..","..y+shy..")" end) if res.rota then text=text:gsub("\\frz([%d%.%-]+)",function(z) return "\\frz"..z+shr end) if not text:match("^{[^}]-\\frz") then text=addtag("\\frz"..shr,text) end end if res.sca then text=text:gsub("(\\fscx)([%d%.%-]+)",function(x,y) return x..y+shsx end) text=text:gsub("(\\fscy)([%d%.%-]+)",function(x,y) return x..y+shsy end) if not nontra:match("^{[^}]-\\fscx") then text=addtag3("\\fscx"..shsx+100,text) end if not nontra:match("^{[^}]-\\fscy") then text=addtag3("\\fscy"..shsy+100,text) end end text=text:gsub("([%d%.%-]+%d)([\\}%),])",function(a,b) return round(a,2)..b end) end

-- shake rotation if res.posi=="shake rotation" then s=line.start_time ang=ps angx=res.eks angy=res.wai angl={ang,angx,angy} rots={"\\frz","\\frx","\\fry"} for r=1,3 do ro=rots[r] an=angl[r] if an~=0 then shr=math.random(-100,100)/100*an if res.layers then ch=0 for p=1,#shaker do sv=shaker[p] if sv[1]==s and sv[2]==r then ch=1 shr=sv[3] end end if ch==0 then rt=r a={s,rt,shr} table.insert(shaker,a) end end text=text:gsub(ro.."([%d%.%-]+)",function(z) return ro..z+shr end) text=addtag3(ro..shr,text) end end end

-- shadow layer if res.posi=="shadow layer" then sr=stylechk(subs,line.style) text=text:gsub("\\1c","\\c") shadcol=nontra:match("^{[^}]-\\4c(&H%x+&)") or sr.color4:gsub("H%x%x","H") if ps~=0 then xsh=ps ysh=ps else xsh=res.eks ysh=res.wai end if xsh==0 and ysh==0 then xs=nontra:match("^{[^}]-\\xshad([%d%.%-]+)") or nontra:match("^{[^}]-\\shad([%d%.]+)") or sr.shadow ys=nontra:match("^{[^}]-\\yshad([%d%.%-]+)") or nontra:match("^{[^}]-\\shad([%d%.]+)") or sr.shadow else xs=xsh ys=ysh end

if tonumber(xs)>0 and tonumber(ys)>0 then text=text:gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end) if not text:match("\\pos") and not text:match("\\move") then text=getpos(subs,text) end text=text:gsub("\\[xy]?shad([%d%.%-]+)","") l2=line text2=text

text2=text2 :gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return "\\pos("..a+xs..","..b+ys..")" end) :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) return "\\pos("..a+xs..","..b+ys..","..c+xs..","..d+ys end) :gsub(ATAG,function(tag) if tag:match("\\4c&H%x+&") then tag=tag:gsub("\\3?c&H%x+&","") :gsub("\\4c(&H%x+&)","\\c%1\\3c%1") else tag=tag:gsub("\\3?c&H%x+&","") end if tag:match("\\4a&H%x+&") then tag=tag:gsub("\\alpha&H%x+&","") :gsub("\\4a(&H%x+&)","\\alpha%1") end tag=tag:gsub("\\[13]a&H%x+&","") :gsub("{}","") return tag end)

text2=addtag3("\\c"..shadcol,text2) text2=addtag3("\\3c"..shadcol,text2) text=text:gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end)

l2.text=text2 subs.insert(i+1,l2) line.layer=line.layer+1 sel=shiftsel(sel,i,0) nsel=shiftsel(nsel,i,1) end if z==#sel then sel=nsel end end

-- fbf X <--> Y if res.posi=="fbf X <--> Y" then newY=XYtab[z].x-Xref+Yref if res.rota then newY=Yref-(XYtab[z].x-Xref) end newX=XYtab[z].y-Yref+Xref if res.rota then newX=Xref-(XYtab[z].y-Yref) end text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)","\\pos("..newX..","..newY..")") end

-- Space out letters if res.posi=="space out letters" then sr=stylechk(subs,line.style) acalign=nil text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),.-%)","\\pos(%1,%2)") :gsub("%s?\\[Nn]%s?"," ") if not text:match"\\pos" then text=getpos(subs,text) end tags=text:match(STAG) or "" after=text:gsub(STAG,"") :gsub("{[^\\}]-}","") local px,py=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") local x1,width,w,wtotal,let,spacing,avgspac,ltrspac,xpos,lastxpos,spaces,prevlet,scx,k1,k2,k3,bord,off,inwidth,wdiff,pp,tpos scx=text:match("\\fscx([%d%.]+)") or sr.scale_x k1,k2,k3=text:match("clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),") bord=text:match("^{[^}]-\\bord([%d%.]+)") or sr.outline visible=text:gsub("%b{}","") letters={} wtotal=0 letrz=re.find(visible,".") for l=1,#letrz do w=aegisub.text_extents(sr,letrz[l].str) table.insert(letters,{l=letrz[l].str,w=w}) wtotal=wtotal+w end intags={} cnt=0 for chars,tag in after:gmatch("([^}]+)({\\[^}]+})") do pp=re.find(chars,".") tpos=#pp+1+cnt intags[tpos]=tag cnt=cnt+#pp end spacing=ps avgspac=wtotal/#letters off=(letters[1].w-letters[#letters].w)/4*scx/100 inwidth=(wtotal-letters[1].w/2-letters[#letters].w/2)*scx/100 if spacing==1 then spacing=round(avgspac*scx)/100 end width=(#letters-1)*spacing --off

-- klip-based stuff if k1 then width=(k3-k1)-letters[1].w/2*(scx/100)-letters[#letters].w/2*(scx/100)-(2*bord) spacing=(width+2*bord)/(#letters-1) px=(k1+k3)/2-off tags=tags:gsub("\\i?clip%b()","") end

-- find starting x point based on alignment if not acalign then acalign=text:match("\\an(%d)") or sr.align end acalign=tostring(acalign) if acalign:match("[147]") then tags=tags:gsub("\\an%d","") :gsub("^{","{\\an"..acalign+1) :gsub("\\pos%(([%d%.%-]+)",function(p) return "\\pos("..round(p+(wtotal/2)*(scx/100),2) end) end if acalign:match("[369]") then tags=tags:gsub("\\an%d","") :gsub("^{","{\\an"..acalign-1) :gsub("\\pos%(([%d%.%-]+)",function(p) return "\\pos("..round(p-(wtotal/2)*(scx/100),2) end) end if not k1 then px,py=tags:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") end acalign=tags:match("\\an(%d)") x1=round(px-width/2) wdiff=(width-inwidth)/(#letters-1) lastxpos=x1 spaces=0 -- weird letter-width sorcery starts here for t=1,#letters do let=letters[t] if t>1 then prevlet=letters[t-1] ltrspac=(let.w+prevlet.w)/2*scx/100+wdiff ltrspac=round(ltrspac,2) else fact1=spacing/(avgspac*scx/100) fact2=(let.w-letters[#letters].w)/4*scx/100 ltrspac=round(fact1*fact2,2) end if intags[t] then tags=tags..intags[t] tags=tags:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") tags=duplikill(tags) end t2=tags..let.l xpos=lastxpos+ltrspac t2=t2:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)","\\pos("..xpos..",%2)") lastxpos=xpos l2=line l2.text=t2 if t==1 then text=t2 else if let.l~=" " then subs.insert(i+t-1-spaces,l2) else count=count-1 spaces=spaces+1 end end end count=count+#letters-1 end

-- Replicate if res.posi=="replicate" then startf=ms2fr(line.start_time) endf=ms2fr(line.end_time) if moverep then x1,y1,mx,my=text:match("\\move%(([^,]+),([^,]+),([^,]+),([^,%)]+)") if not mx then t_error("Abort: No \\move tag on line "..z,1) end rxdist=mx rydist=my text=text:gsub("\\move%b()","\\pos("..x1..","..y1..")") else if not text:match("\\pos") and not text:match("\\move") then text=getpos(subs,text) end x1,y1=text:match("\\pos%(([^,]+),([^,]+)%)") if not x1 then x1,y1=text:match("\\move%(([^,]+),([^,]+),") end if dtype==1 then rxdist=xdist*(repl-1)+x1 rydist=ydist*(repl-1)+y1 else if xabs then rxdist=xdist else rxdist=xdist+x1 end if yabs then rydist=ydist else rydist=ydist+y1 end end end -- replicating for r=repl,1,-1 do l2=line posx=numgrad(x1,rxdist,repl,r,xcel) posy=numgrad(y1,rydist,repl,r,ycel) text2=text:gsub("\\pos%b()","\\pos("..posx..","..posy..")") :gsub("\\move%(([^,]+),([^,]+),([^,]+),([^,%)]+)",function(m1,m2,m3,m4) return "\\move("..posx..","..posy..","..m3-m1+posx..","..m4-m2+posy end) startf2=startf+replay*(r-1) start2=fr2ms(startf2) end2=fr2ms(endf) if endrep then if endf

-- FBF Retrack if res.posi=="fbf retrack" then frame=ms2fr(line.start_time) fpos,total=detrack(z,sel,retrack,frame) if not text:match("\\pos") then text=getpos(text) end posix,posiy=text:match("\\pos%(([^,]+),([^,]+)%)") if fpos>1 and fpos1 then smf=1 end if smf<0 then smf=0 end ref=round(math.abs(ps)) if ref<1 then ref=1 end for re=1,ref do sm=smf*(1/re) if re

line.text=text subs[i]=line end table.sort(sel) if res.posi=="space out letters" then last=sel[#sel] for s=1,count do table.insert(sel,last+s) end end return sel end function bilocator(subs,sel) xx=res.eks yy=res.wai rM=res.move if rM=="transmove" and sel[#sel]==#subs then table.remove(sel,#sel) end for z=#sel,1,-1 do i=sel[z] progress("Moving through hyperspace... "..(#sel-z+1).."/"..#sel) line=subs[i] text=line.text start=line.start_time endt=line.end_time

if rM=="transmove" and i<#subs then

nextline=subs[i+1] text2=nextline.text text=text:gsub("\\1c","\\c") text2=text2:gsub("\\1c","\\c") movt1,movt2=gettimes(start,endt) if res.times then movt=","..movt1..","..movt2 else movt="" end

-- move p1=text:match("\\pos%((.-)%)") p2=text2:match("\\pos%((.-)%)") if p1==nil or p2==nil then t_error("Missing \\pos tag(s).",1) end if p2~=p1 then text=text:gsub("\\pos%((.-)%)","\\move(%1,"..p2..movt..")") end

-- transforms tf=""

tftags={"fs","fsp","fscx","fscy","blur","bord","shad","fax","fay"} for tg=1,#tftags do t=tftags[tg] if text2:match("\\"..t.."[%d%.%-]+") then tag2=text2:match("(\\"..t.."[%d%.%-]+)") if text:match("\\"..t.."[%d%.%-]+") then tag1=text:match("(\\"..t.."[%d%.%-]+)") else tag1="" end if tag1~=tag2 then tf=tf..tag2 end end end

tfctags={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} for tg=1,#tfctags do t=tfctags[tg] if text2:match("\\"..t.."&H%x+&") then tag2=text2:match("(\\"..t.."&H%x+&)") if text:match("\\"..t.."&H%x+&") then tag1=text:match("(\\"..t.."&H%x+&)") else tag1="" end if tag1~=tag2 then tf=tf..tag2 end end end

tfrtags={"frz","frx","fry"} for tg=1,#tfrtags do t=tfrtags[tg] if text2:match("\\"..t.."[%d%.%-]+") then tag2=text2:match("(\\"..t.."[%d%.%-]+)") rr2=tonumber(text2:match("\\"..t.."([%d%.%-]+)")) if text:match("\\"..t.."[%d%.%-]+") then tag1=text:match("(\\"..t.."[%d%.%-]+)") rr1=tonumber(text:match("\\"..t.."([%d%.%-]+)")) else tag1="" rr1=0 end if tag1~=tag2 then if res.rotac and math.abs(rr2-rr1)>180 then if rr2>rr1 then rr2=rr2-360 tag2="\\frz"..rr2 else rr1=rr1-360 text=text:gsub("\\frz[%d%.%-]+","\\frz"..rr1) end end tf=tf..tag2 end end end

-- apply transform if tf~="" then text=text:gsub("^({\\[^}]-)}","%1\\t("..movt:gsub("^,(.*)","%1,")..tf..")}") end

-- delete line 2 if res.keep==false then subs.delete(i+1) if i<#sel then for s=i+1,#sel do sel[s]=sel[s]-1 end end end

end -- end of transmove

if rM=="horizontal" then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)","\\move(%1,%2,%3,%2") end if rM=="vertical" then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)","\\move(%1,%2,%1,%4") end if rM=="rvrs. move" then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)","\\move(%3,%4,%1,%2") end

if rM=="clip2move" then movt1,movt2=gettimes(start,endt) if res.times then M=","..movt1..","..movt2 else M="" end if not text:match("\\clip") then t_error("Missing \\clip.",true) end cx1,cy1,cx2,cy2=text:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+)") xmov=cx2-cx1 ymov=cy2-cy1 text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)[^%)]*",function(x,y) return "\\move("..x..","..y..","..round(x+xmov,2)..","..round(y+ymov,2)..M end) :gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)",function(x,y) return "\\move("..x..","..y..","..round(x+xmov,2)..","..round(y+ymov,2)..M end) :gsub("\\clip%b()","") end

if rM=="shiftstart" then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)([^%)]*)",function(a,b,c,d,e) if res.videofr then vfcheck() e=e:gsub("([%d%.%-]+)(,[%d%.%-]+)",videopos.."%2") end return "\\move("..a+xx..","..b+yy..","..c..","..d..e end) end

if rM=="shiftmove" or rM=="move to" then movt1,movt2=gettimes(start,endt) if res.videofr then vfcheck() end if res.times or res.videofr then M=","..movt1..","..movt2 else M="" end text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)([^%)]*)",function(a,b,c,d,e) if res.videofr then if e~="" then M=e end M=M:gsub("([%d%.%-]+,)([%d%.%-]+)","%1"..videopos) end if rM=="move to" then c=xx d=yy else c=c+xx d=d+yy end return "\\move("..a..","..b..","..c..","..d..M end) text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)",function(a,b) if res.videofr then M=M:gsub("([%d%.%-]+,)([%d%.%-]+)","%1"..videopos) end if rM=="move to" then c=xx d=yy else c=a+xx d=b+yy end return "\\move("..a..","..b..","..c..","..d..M end) end

if rM=="move clip" then m1,m2,m3,m4=text:match("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)") mt=text:match("\\move%([^,]+,[^,]+,[^,]+,[^,]+,([%d%.,%-]+)") if mt==nil then mt="" else mt=mt.."," end klip=text:match("\\i?clip%([%d%.,%-]+%)") klip=klip:gsub("(\\i?clip%()([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d,e) return a..b+m3-m1..","..c+m4-m2..","..d+m3-m1..","..e+m4-m2 end) text=addtag("\\t("..mt..klip..")",text) end

text=roundpar(text,2) line.text=text subs[i]=line end end function multimove(subs,sel) text=subs[sel[1]].text if not text:match("\\move") then t_error("Missing \\move tag on line 1",1) end x1,y1,x2,y2,t=text:match("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)([^%)]*)%)") m1=x2-x1 m2=y2-y1 poscheck=0 for z=2,#sel do progress("Synchronizing movement... "..z.."/"..#sel) line=subs[sel[z]] text=line.text p1,p2=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if p1 then text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%- ]+)%)","\\move%(%1,%2,"..round(p1+m1,2)..","..round(p2+m2,2)..t.."%)") else poscheck=1 end line.text=text subs[sel[z]]=line end if poscheck==1 then t_error("Some lines are missing \\pos tags") end end function randomove(subs,sel) T=true RMGUI={ {x=0,y=0,width=2,class="checkbox",name="rmt",label="Time:",value=rmt}, {x=2,y=0,width=4,class="intedit",name="slow",value=slowd or 50,hint="max slowdown to ... %",max=100,min=1}, {x=6,y=0,class="label",label="%"},

{x=0,y=1,width=2,class="checkbox",name="rms",label="Space:",value=rms}, {x=2,y=1,class="checkbox",name="rm1",label="x1"}, {x=3,y=1,class="checkbox",name="rm2",label="y1"}, {x=4,y=1,class="checkbox",name="rm3",label="x2",value=T}, {x=5,y=1,class="checkbox",name="rm4",label="y2",value=T},

{x=0,y=2,width=2,class="label",label=" Distance:"}, {x=2,y=2,width=4,class="floatedit",name="rmdist",value=rmd or 0}, {x=0,y=3,width=2,class="label",label=" Direction:"}, {x=2,y=3,width=2,class="checkbox",name="plus",label="positive",value=T}, {x=4,y=3,width=2,class="checkbox",name="minus",label="negative",value=T},

{x=0,y=4,width=7,height=4,class="textbox",value="Time - \\move direction doesn't change.\n'50%' means text will move between 50 and 100% of original distance.\n\nSpace - Given coordinates change within given distance and direction.\n\nTime and Space may be combined, but it makes more sense to use just one."} } P,rez=ADD(RMGUI,{"OK","Cancel"},{ok='OK',close='Cancel'}) if P=="Cancel" then ak() end

rmt=rez.rmt rms=rez.rms slowd=rez.slow rmd=rez.rmdist rmdp=rez.plus rmdm=rez.minus

if not rmt and not rms then t_error("Neither Time nor Space selected.\nSpace-time travel failed.",1) end

plus=0 minus=0 if rmdp then plus=rmd*100 end if rmdm then minus=(0-rmd)*100 end

for z,i in ipairs(sel) do progress("Randomizing movement... "..z.."/"..#sel) line=subs[i] text=line.text if text:match("\\move") then if rmt then movt1,movt2=gettimes(line.start_time,line.end_time) text=text:gsub("(\\move%([%d%.%-]+,[%d%.%-]+,[%d%.%-]+,[%d%.%-]+)%)","%1,"..movt1..","..movt2..")") movt3=math.random(movt2,movt2*(100/slowd)) text=text:gsub("(\\move%([%d%.%-]+,[%d%.%-]+,[%d%.%-]+,[%d%.%-]+),([%d%.,%- ]+)%)","%1,"..movt1..","..movt3..")") end if rms then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) if rez.rm1 then a=a+math.random(minus,plus)/100 end if rez.rm2 then b=b+math.random(minus,plus)/100 end if rez.rm3 then c=c+math.random(minus,plus)/100 end if rez.rm4 then d=d+math.random(minus,plus)/100 end return "\\move("..a..","..b..","..c..","..d end) end end line.text=text subs[i]=line end end function modifier(subs,sel) post=res.post force=res.force xx=res.eks yy=res.wai _,rr=res.rndec:gsub("0","") FR={"frx","fry","frz"} for z,i in ipairs(sel) do progress("Morphing... "..z.."/"..#sel) line=subs[i] text=line.text

if res.mod=="round numbers" then if text:match("\\pos") and res.rnd=="all" or text:match("\\pos") and res.rnd=="pos" then text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return "\\pos("..round(a,rr)..","..round(b,rr)..")" end) end if text:match("\\org") and res.rnd=="all" or text:match("\\org") and res.rnd=="org" then text=text:gsub("\\org%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return "\\org("..round(a,rr)..","..round(b,rr)..")" end) end if text:match("\\move") and res.rnd=="all" or text:match("\\move") and res.rnd=="move" then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)",function(mo1,mo2,mo3,mo4) return "\\move("..round(mo1,rr)..","..round(mo2,rr)..","..round(mo3,rr)..","..round(mo4,rr) end) end if text:match("\\i?clip") and res.rnd=="all" or text:match("\\i?clip") and res.rnd=="clip" then for klip in text:gmatch("\\i?clip%([^%)]+%)") do if klip:match("m") then rrr=0 else rrr=rr end klip2=klip:gsub("([%d%.%-]+)",function(c) return round(c,rrr) end) klip=esc(klip) text=text:gsub(klip,klip2) end end if text:match("\\p1") and res.rnd=="all" or text:match("\\p1") and res.rnd=="mask" then tags=text:match(STAG) text=text:gsub(STAG,"") :gsub("([%d%.%-]+)",function(m) return round(m,rr) end) text=tags..text end end

if res.mod=="fullmovetimes" or res.mod=="fulltranstimes" then start=line.start_time endt=line.end_time startf=ms2fr(start) endf=ms2fr(endt) start2=fr2ms(startf) endt2=fr2ms(endf-1) tim=fr2ms(1) movt1=start2-start+tim movt2=endt2-start+tim movt=movt1..","..movt2 end

if res.mod=="killmovetimes" then text=text:gsub("\\move%(([^,]+,[^,]+,[^,]+,[^,]+),[^,]+,[^,%)]+","\\move(%1") end if res.mod=="fullmovetimes" then text=text:gsub("\\move%(([^,]+,[^,]+,[^,]+,[^,]+).-%)","\\move(%1,"..movt..")") end if res.mod=="killtranstimes" then text=text:gsub("\\t%([%d%.%-]-,[%d%.%-]-,","\\t(") end if res.mod=="fulltranstimes" then text=text :gsub("\\t%([%d%.%-]-,[%d%.%-]-,([%d%.]-,)\\","\\t("..movt..",%1\\") :gsub("\\t%([%d%.%-]-,[%d%.%-]-,\\","\\t("..movt..",\\") :gsub("\\t%(([%d%.]-,)\\","\\t("..movt..",%1\\") :gsub("\\t%(\\","\\t("..movt..",\\") end

if res.mod=="move v. clip" then if z==1 then v1,v2=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if v1==nil then t_error("Error. No \\pos tag on line 1.",true) end end if z~=1 and text:match("\\pos") then v3,v4=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") V1=v3-v1 V2=v4-v2 if text:match("clip%(m [%d%a%s%-%.]+%)") then ctext=text:match("clip%(m ([%d%a%s%-%.]+)%)") ctext2=ctext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) return a+V1.." "..b+V2 end) ctext=ctext:gsub("%-","%%-") text=text:gsub("clip%(m "..ctext,"clip(m "..ctext2) end if text:match("clip%(%d+,m [%d%a%s%-%.]+%)") then fac,ctext=text:match("clip%((%d+),m ([%d%a%s%-%.]+)%)") factor=2^(fac-1) ctext2=ctext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) return a+factor*V1.." "..b+factor*V2 end) ctext=ctext:gsub("%-","%%-") text=text:gsub(",m "..ctext,",m "..ctext2) end end end

if res.mod=="set origin" then text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)", function(a,b) return "\\pos("..a..","..b..")\\org("..a+res.eks..","..b+res.wai..")" end) end

if res.mod=="calculate origin" then local c={} local c2={} x1,y1,x2,y2,x3,y3,x4,y4=text:match("clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+)") cor1={x=tonumber(x1),y=tonumber(y1)} table.insert(c,cor1) table.insert(c2,cor1) cor2={x=tonumber(x2),y=tonumber(y2)} table.insert(c,cor2) table.insert(c2,cor2) cor3={x=tonumber(x3),y=tonumber(y3)} table.insert(c,cor3) table.insert(c2,cor3) cor4={x=tonumber(x4),y=tonumber(y4)} table.insert(c,cor4) table.insert(c2,cor4) table.sort(c,function(a,b) return tonumber(a.x)

-- x/y factor / angle / whatever fx1=math.abs(disty1/distx1) fx2=math.abs(disty2/distx2) fx3=math.abs(distx3/disty3) fx4=math.abs(distx4/disty4)

-- determine if y is going up or down cy=1 if c[2].y>c[1].y then cx1=round(xx1-(yy1-cy)/fx1) else cx1=round(xx1+(yy1-cy)/fx1) end if c[4].y>c[3].y then cx2=round(xx2-(yy2-cy)/fx2) else cx2=round(xx2+(yy2-cy)/fx2) end top=cx2-cx1 cy=500 if c[2].y>c[1].y then cx1=round(xx1-(yy1-cy)/fx1) else cx1=round(xx1+(yy1-cy)/fx1) end if c[4].y>c[3].y then cx2=round(xx2-(yy2-cy)/fx2) else cx2=round(xx2+(yy2-cy)/fx2) end bot=cx2-cx1 if top>bot then cy=c2[4].y ycalc=1 else cy=c2[1].y ycalc=-1 end

-- LOOK FOR ORG X repeat if c[2].y>c[1].y then cx1=round(xx1-(yy1-cy)/fx1) else cx1=round(xx1+(yy1-cy)/fx1) end if c[4].y>c[3].y then cx2=round(xx2-(yy2-cy)/fx2) else cx2=round(xx2+(yy2-cy)/fx2) end cy=cy+ycalc until cx1>=cx2 or math.abs(cy)==50000 org1=cx1

-- determine if x is going left or right cx=1 if c2[2].x>c2[1].x then cy1=round(yy3-(xx3-cx)/fx3) else cy1=round(yy3+(xx3-cx)/fx3) end if c2[4].x>c2[3].x then cy2=round(yy4-(xx4-cx)/fx4) else cy2=round(yy4+(xx4-cx)/fx4) end left=cy2-cy1 cx=500 if c2[2].x>c2[1].x then cy1=round(yy3-(xx3-cx)/fx3) else cy1=round(yy3+(xx3-cx)/fx3) end if c2[4].x>c2[3].x then cy2=round(yy4-(xx4-cx)/fx4) else cy2=round(yy4+(xx4-cx)/fx4) end rite=cy2-cy1 if left>rite then cx=c[4].x xcalc=1 else cx=c[1].x xcalc=-1 end

-- LOOK FOR ORG Y repeat if c2[2].x>c2[1].x then cy1=round(yy3-(xx3-cx)/fx3) else cy1=round(yy3+(xx3-cx)/fx3) end if c2[4].x>c2[3].x then cy2=round(yy4-(xx4-cx)/fx4) else cy2=round(yy4+(xx4-cx)/fx4) end cx=cx+xcalc until cy1>=cy2 or math.abs(cx)==50000 org2=cy1

text=text:gsub("\\org%([^%)]+%)","") text=addtag("\\org("..org1..","..org2..")",text) end if res.mod=="set rotation" then rotinhell() rota=res.freeze for f=1,3 do rot=FR[f] if res[rot] then text=addtag3("\\"..rot..rota,text) end end end

if res.mod=="rotate 180" then rotinhell() nontra=text:gsub("\\t%b()","") for f=1,3 do rot=FR[f] if res[rot] then if nontra:match("\\"..rot) then text=flip(rot,text) else text=addtag3("\\"..rot.."180",text) end end end end

if res.mod=="negative rot" then rotinhell() for f=1,3 do rot=FR[f] if res[rot] then text=negative(text,180,"\\"..rot) end end end

if res.mod=="vector2rect." then text=text:gsub("\\(i?)clip%(m ([%d%.%-]+) ([%d%.%-]+) l ([%d%.%-]+) ([%d%.%-]+) ([%d%.%-]+) ([%d%.%-]+) ([%d%.%-]+) ([%d%.%-]+)%)","\\%1clip(%2,%3,%6,%7)") end

if res.mod=="rect.2vector" then text=text:gsub("\\(i?)clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(ii,a,b,c,d) a,b,c,d=round4(a,b,c,d) return string.format("\\"..ii.."clip(m %d %d l %d %d %d %d %d %d)",a,b,c,b,c,d,a,d) end) end

if res.mod=="clip scale" and text:match("\\i?clip%(%d-,?m") then oscf=text:match("\\i?clip%((%d+),m") if oscf then fact1=2^(oscf-1) else fact1=1 end force=round(force) if force<1 then force=1 end fact2=2^(force-1) text=text:gsub("(\\i?clip%()(%d*,?)m ([^%)]+)%)",function(a,b,c) return a..force..",m "..c:gsub("([%d%.%-]+)",function(d) return round(d/fact1*fact2) end)..")" end) :gsub("1,m","m") end

if res.mod=="find centre" then text=text:gsub("\\pos%([^%)]+%)","") t2=text text=text:gsub("\\clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) x=round(a/2+c/2) y=round(b/2+d/2) return "\\pos("..x..","..y..")" end) if t2==text then t_error("Requires rectangular clip",1) end end

if res.mod=="extend mask" then if xx==0 and yy==0 then t_error("Error. Both given values are 0.\nUse the Teleporter X and Y fields.",true) end draw=text:match("}m ([^{]+)") draw2=draw:gsub("([%d%.%-]+) ([%d%.%-]+)",function(a,b) if tonumber(a)>0 then ax=xx elseif tonumber(a)<0 then ax=0-xx else ax=0 end if tonumber(b)>0 then by=yy elseif tonumber(b)<0 then by=0-yy else by=0 end return a+ax.." "..b+by end) draw=esc(draw) text=text:gsub("(}m )"..draw,"%1"..draw2) end

if res.mod=="flip mask" then draw=text:match("}m ([^{]+)") draw2=draw:gsub("([%d%.%-]+) ([%d%.%-]+)",function(a,b) return 0-a.." "..b end) draw=esc(draw) text=text:gsub("(}m )"..draw,"%1"..draw2) end

if res.mod=="adjust drawing" then if not text:match("\\p%d") then ak() end -- drawing 2 clip if not text:match("\\i?clip") then klip="\\clip("..text:match("\\p1[^}]-}(m [^{]*)")..")" scx=text:match("\\fscx([%d%.]+)") or 100 scy=text:match("\\fscy([%d%.]+)") or 100 if text:match("\\pos") then local xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") xx=round(xx) yy=round(yy) coord=klip:match("\\clip%(m ([^%)]+)%)") coord2=coord:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return round(a*scx/100+xx).." "..round(b*scy/100+yy) end) coord=coord:gsub("%-","%%-") klip=klip:gsub(coord,coord2) end if not text:match("\\pos") then text=text:gsub("^{","{\\pos(0,0)") end text=addtag(klip,text) -- clip 2 drawing else text=text:gsub("\\i?clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) a,b,c,d=round4(a,b,c,d) return string.format("\\clip(m %d %d l %d %d %d %d %d %d)",a,b,c,b,c,d,a,d) end) klip=text:match("\\i?clip%((m.-)%)") if text:match("\\pos") then local xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") xx=round(xx) yy=round(yy) coord=klip:match("m ([%d%a%s%-]+)") coord2=coord:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return a-xx.." "..b-yy end) coord=coord:gsub("%-","%%-") klip=klip:gsub(coord,coord2) end text=text:gsub("(\\p1[^}]-})(m [^{]*)","%1"..klip) if not text:match("\\pos") then text=text:gsub("^{","{\\pos(0,0)") end if text:match("\\an") then text=text:gsub("\\an%d","\\an7") else text=text:gsub("^{","{\\an7") end if text:match("\\fscx") then text=text:gsub("\\fscx[%d%.]+","\\fscx100") else text=text:gsub("\\p1","\\fscx100\\p1") end if text:match("\\fscy") then text=text:gsub("\\fscy[%d%.]+","\\fscy100") else text=text:gsub("\\p1","\\fscy100\\p1") end text=text:gsub("\\i?clip%(.-%)","") end end

if res.mod=="randomask" then draw=text:match("}m ([^{]+)") draw2=draw:gsub("([%d%.%-]+)",function(a) return a+math.random(0-force,force) end) draw=esc(draw) text=text:gsub("(}m )"..draw,"%1"..draw2) end

if res.mod=="randomize..." then if z==1 then randomgui={ {x=0,y=0,class="label",label="randomization value"}, {x=1,y=0,class="floatedit",name="random",value=rnd or 3}, {x=0,y=1,class="label",label="rounding"}, {x=1,y=1,class="dropdown",name="dec",items={"1","0.1","0.01","0.001"},value=rd or "0.1",}, {x=1,y=2,class="edit",name="randomtag",value=rt,hint="separate multiple tags with a comma"}, {x=1,y=3,class="edit",name="partag1",hint="pos, move, org, clip, (fad)",value=rtx}, {x=1,y=4,class="edit",name="partag2",hint="pos, move, org, clip, (fad)",value=rty}, {x=0,y=2,class="checkbox",name="ntag",label="standard type tag - \\",value=rnt,hint="\\[tag][number]"}, {x=0,y=3,class="checkbox",name="ptag1",label="parenthesis tag x - \\",value=rpt1,hint="\\tag(X,y)"}, {x=0,y=4,class="checkbox",name="ptag2",label="parenthesis tag y - \\",value=rpt2,hint="\\tag(x,Y)"} } press,rez=ADD(randomgui,{"Randomize","Disintegrate"},{ok='Randomize',close='Disintegrate'}) if press=="Disintegrate" then ak() end rt=rez.randomtag:gsub(" ","") rtx=rez.partag1 rty=rez.partag2 rd=rez.dec _,deci=rez.dec:gsub("0","") rnd=rez.random rnt=rez.ntag rpt1=rez.ptag1 rpt2=rez.ptag2 end

-- standard tags if rez.ntag then for tg in rt:gmatch("[^,]+") do text=text:gsub("(\\"..tg..")([%d%.%-]+)",function(tag,val) rndm=math.random(-100,100)/100*rnd nval=val+rndm if nval<0 and noneg:match(tag) then nval=math.abs(nval) end return tag..round(nval,deci) end) text=text:gsub("(\\"..tg..")(&H%x%x&)",function(tag,val) rndm=math.random(-100,100)/100*rnd val=val:match("&H(%x%x)&") nval=(tonumber(val,16)) nval=round(nval+rndm) if nval<0 then nval=0 end if nval>255 then nval=255 end nval=tohex(nval) return tag.."&H"..nval.."&" end) end end

-- parenthesis tags if rez.ptag1 or rez.ptag2 then rndm=math.random(-100,100)/100*rnd if rez.ptag1 then text=text:gsub("\\"..rtx.."%(([%d%.%-]+),([%d%.%-]+)", function(x,y) return "\\"..rtx.."("..round((x+rndm),deci)..","..y end) :gsub("\\"..rtx.."%(([%d%.%-]+,[%d%.%-]+,)([%d%.%-]+),([%d%.%-]+)", function(a,x,y) return "\\"..rtx.."("..a..round((x+rndm),deci)..","..y end) end if rez.ptag2 then text=text:gsub("\\"..rty.."%(([%d%.%-]+),([%d%.%-]+)", function(x,y) return "\\"..rty.."("..x..","..round((y+rndm),deci) end) :gsub("\\"..rty.."%(([%d%.%-]+,[%d%.%-]+,)([%d%.%-]+),([%d%.%-]+)", function(a,x,y) return "\\"..rty.."("..a..x..","..round((y+rndm),deci) end) end end end

if res.mod=="letterbreak" then text=text:gsub("%s*\\N%s*"," ") :gsub("^([^{]*)",function(t) return re.sub(t,"([\\w[:punct:]\\s])","\\1\\\\N") end) :gsub("}([^{]*)",function(t) return "}"..re.sub(t,"([\\w[:punct:]\\s])","\\1\\\\N") end) :gsub("\\N$","") end

if res.mod=="wordbreak" then text=text:gsub(" *$","") :gsub("^([^{]*)",function(t) return t:gsub("%s+"," \\N") end) :gsub("(}[^{]*)",function(t) return t:gsub("%s+"," \\N") end) end

line.text=text subs[i]=line end end function movetofbf(subs,sel) fra={} for z=#sel,1,-1 do i=sel[z] progress("Dissecting line... "..(#sel-z+1).."/"..#sel) line=subs[i] text=line.text styleref=stylechk(subs,line.style) start=line.start_time endt=line.end_time startf=ms2fr(start) endf=ms2fr(endt) frames=endf-1-startf frnum=frames table.insert(fra,frnum) l2=line frdiff=(fr2ms(startf+1)-fr2ms(startf))/2 -- Real Start, End, Duration RS=fr2ms(startf) RE=fr2ms(endf) RD=RE-RS

for frm=endf-1,startf,-1 do l2.text=text -- move if text:match("\\move") then m1,m2,m3,m4=text:match("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)") mvstart,mvend=text:match("\\move%([%d%.%-]+,[%d%.%-]+,[%d%.%-]+,[%d%.%-]+,([%d%.%- ]+),([%d%.%-]+)") if mvstart==nil then mvstart=0 end if mvend==nil then mvend=RD end moffset=mvstart CS=fr2ms(startf+frnum) frcount=CS-RS mlimit=tonumber(mvend) mpart=frcount-tonumber(mvstart)+frdiff mwhole=mvend-mvstart pos1=round((((m3-m1)/mwhole)*mpart+m1),2) pos2=round((((m4-m2)/mwhole)*mpart+m2),2) xdiff=pos1-m1 ydiff=pos2-m2 if mpart<0 then pos1=m1 pos2=m2 end if mpart>mlimit-moffset then pos1=m3 pos2=m4 end l2.text=text:gsub("\\move%([^%)]*%)","\\pos("..pos1..","..pos2..")") if res.c2fbf and frm>1 then l2.text=l2.text:gsub("(\\i?clip%()([^%)]*)%)",function(kl,ip) ip=ip:gsub("([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(a,b,c,d) return a+xdiff..","..b+ydiff..","..c+xdiff..","..d+ydiff end) :gsub("([%d%.%-]+) ([%d%.%-]+)",function(a,b) return round(a+xdiff).." "..round(b+ydiff) end) return kl..ip..")" end) end end -- fade if text:match("\\fad%(") then l2.text=l2.text:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) f1,f2=text:match("\\fad%(([%d%.]+),([%d%.]+)") linealfa=text:match("^{[^}]-\\alpha(&H%x%x&)") or "&H00&" l2.text=l2.text :gsub("^({[^}]-)\\alpha&H%x%x&","%1") :gsub("{(.-)\\fad%b()(.-)}","{\\alpha&HFF&%1%2\\t(0,"..f1..",\\alpha"..linealfa..")\\t("..RD- f2..",0,\\alpha&HFF&)}") :gsub("(.){(.-)(\\alpha&H%x%x&)(.-)}","%1{%2\\alpha&HFF&\\t(0,"..f1..",%3)\\t("..RD- f2..",0,\\alpha&HFF&)%4}") for c=1,4 do l2.text,c=l2.text:gsub("{(.-)(\\"..c.."a)(&H%x%x&)(.-)}","{%1%2&HFF&\\t(0,"..f1..",%2%3)\\t("..RD- f2..",0,%2&HFF&)%4}") end l2.text=l2.text:gsub("|","\\") end

lastags2="" l2.text=l2.text:gsub(ATAG,function(tg) return cleantr(tg) end) :gsub(ATAG,function(tg) if tg:match("\\t") then return terraform(tg) else return tg end end)

l2.start_time=fr2ms(frm) l2.end_time=fr2ms(frm+1) subs.insert(i+1,l2) frnum=frnum-1 end line.end_time=endt line.comment=true line.text=text subs[i]=line if res.delfbf then subs.delete(i) end end -- selection sel2={} if res.delfbf then fakt=0 else fakt=1 end for s=#sel,1,-1 do sfr=fra[#sel-s+1] -- shift new sel for s2=#sel2,1,-1 do sel2[s2]=sel2[s2]+sfr+fakt end -- add to new sel for f=1,sfr+fakt do table.insert(sel2,sel[s]+f) end -- add orig line if res.delfbf then table.insert(sel2,sel[s]) end end sel=sel2 return sel end function terraform(tags) ftags="" lastags1="" for tra in tags:gmatch("(\\t%b())") do trstart=0 trend=RD acc=1 trtimes=tra:match("\\t%(([%d,%.]*)") _,ttt=trtimes:gsub(",","") if ttt==1 then acc=tra:match("([%d%.]+)") end if ttt==2 then trstart,trend=tra:match("(%d+),(%d+),") end if ttt==3 then trstart,trend,acc=tra:match("(%d+),(%d+),([%d%.]+),") end if trend=="0" then trend=RD end toffset=trstart CS=fr2ms(startf+frnum) frcount=CS-RS tlimit=tonumber(trend) tpart=frcount-tonumber(trstart)+frdiff twhole=trend-trstart nontra=tags:gsub("\\t%b()","") acc_fac=(tpart-1)^acc/(twhole-1)^acc -- most tags for tg, valt in tra:gmatch("\\(%a+)([%d%.%-]+)") do val1=nil if nontra:match("^{[^}]-\\"..tg) then val1=nontra:match("^{[^}]-\\"..tg.."([%d%.%-]+)") end if lastags2:match("\\"..tg) then val1=lastags2:match("\\"..tg.."([%d%.%-]+)") end if nontra:match("\\"..tg) then val1=nontra:match("\\"..tg.."([%d%.%-]+)") end if lastags1:match("\\"..tg) then val1=lastags1:match("\\"..tg.."([%d%.%-]+)") end if val1==nil then if tg=="bord" or tg=="xbord" or tg=="ybord" then val1=styleref.outline end if tg=="shad" or tg=="xshad" or tg=="yshad" then val1=styleref.shadow end if tg=="fs" then val1=styleref.fontsize end if tg=="fsp" then val1=styleref.spacing end if tg=="frz" then val1=styleref.angle end if tg=="fscx" then val1=styleref.scale_x end if tg=="fscy" then val1=styleref.scale_y end if tg=="blur" or tg=="be" or tg=="fax" or tg=="fay" or tg=="frx" or tg=="fry" then val1=0 end end valf=round(acc_fac*(valt-val1)+val1,2) if tpart<0 then valf=val1 end if tpart>tlimit-toffset then valf=valt end ftags=ftags.."\\"..tg..valf --logg("\n val1: "..val1.." valf: "..valf.." tpart: "..tpart.." twhole: "..twhole) end -- clip if tra:match("\\i?clip%([%d%-]") then ctype,c1,c2,c3,c4=nontra:match("(\\i?clip%()([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)") if not ctype then t_error("Looks like you're transforming a clip that's not set in the first place.",true) end ktype,k1,k2,k3,k4=tra:match("(\\i?clip%()([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)") tc1=round((((k1-c1)/twhole)*tpart+c1),2) tc2=round((((k2-c2)/twhole)*tpart+c2),2) tc3=round((((k3-c3)/twhole)*tpart+c3),2) tc4=round((((k4-c4)/twhole)*tpart+c4),2) if tpart<0 then tc1=c1 tc2=c2 tc3=c3 tc4=c4 end if tpart>tlimit-toffset then tc1=k1 tc2=k2 tc3=k3 tc4=k4 end ftags=ftags..ktype..tc1..","..tc2..","..tc3..","..tc4..")" end -- colour/alpha tra=tra:gsub("\\1c","\\c") nontra=nontra:gsub("\\1c","\\c") for tg, valt in tra:gmatch("\\(%w+)(&H%x+&)") do val1=nil if nontra:match("^{[^}]-\\"..tg.."&") then val1=nontra:match("^{[^}]-\\"..tg.."(&H%x+&)") end if lastags2:match("\\"..tg) then val1=lastags2:match("\\"..tg.."(&H%x+&)") end if nontra:match("\\"..tg) then val1=nontra:match("\\"..tg.."(&H%x+&)") end if lastags1:match("\\"..tg) then val1=lastags1:match("\\"..tg.."(&H%x+&)") end if val1==nil then if tg=="c" then val1=styleref.color1:gsub("H%x%x","H") end if tg=="2c" then val1=styleref.color2:gsub("H%x%x","H") end if tg=="3c" then val1=styleref.color3:gsub("H%x%x","H") end if tg=="4c" then val1=styleref.color4:gsub("H%x%x","H") end if tg=="1a" then val1=styleref.color1:gsub("(H%x%x)%x%x%x%x%x%x","%1") end if tg=="2a" then val1=styleref.color2:gsub("(H%x%x)%x%x%x%x%x%x","%1") end if tg=="3a" then val1=styleref.color3:gsub("(H%x%x)%x%x%x%x%x%x","%1") end if tg=="4a" then val1=styleref.color4:gsub("(H%x%x)%x%x%x%x%x%x","%1") end if tg=="alpha" then val1="&H00&" end end valf=acgrad(val1,valt,twhole,tpart,acc) if tpart<0 then valf=val1 end if tpart>tlimit-toffset then valf=valt end ftags=ftags.."\\"..tg..valf end lastags1=lastags1..ftags lastags1=duplikill(lastags1) end lastags2=lastags2..ftags lastags2=duplikill(lastags2) tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..ftags.."}") tags=duplikill(tags) return tags end function joinfbflines(subs,sel) join=round(res.force) if join<2 then t_error("Minimum of lines to join is 2.\nUse the field at the bottom left to enter a number.",1) end count=1 for z,i in ipairs(sel) do line=subs[i] line.effect=count if z==1 then line.effect="1" end subs[i]=line count=count+1 if count>join then count=1 end end -- delete & time total=#sel for z=#sel,1,-1 do i=sel[z] line=subs[i] if line.effect==tostring(join) then endtime=line.end_time end if i==total then endtime=line.end_time end if line.effect=="1" then line.end_time=endtime line.effect="" subs[i]=line else subs.delete(i) table.remove(sel,#sel) end end return sel end function rotinhell() if not res.frx and not res.fry and not res.frz then t_error("No rotations selected.\nCheck at least one of frx/fry/frz.",1) end end function negative(text,m,rot) text=text:gsub(rot.."([%d%.]+)",function(r) if tonumber(r)>m then return rot..r-360 end end) return text end function transclip(subs,sel,act) line=subs[act] text=line.text if not text:match("\\i?clip%([%d%.%-]+,") then t_error("Error: rectangular clip required on active line.",1) end ctype,cc1,cc2,cc3,cc4=text:match("(\\i?clip)%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)") clipconfig={ {x=0,y=0,width=2,class="label",label=" \\clip("}, {x=2,y=0,width=3,class="edit",name="orclip",value=cc1..","..cc2..","..cc3..","..cc4}, {x=5,y=0,class="label",label=")"}, {x=0,y=1,width=2,class="label",label="\\t(\\clip("}, {x=2,y=1,width=3,class="edit",name="klip",value=cc1..","..cc2..","..cc3..","..cc4}, {x=5,y=1,class="label",label=")"}, {x=0,y=2,width=5,class="label",label="Move x and y for new coordinates by:"}, {x=0,y=3,class="label",label="x:"}, {x=3,y=3,class="label",label="y:"}, {x=1,y=3,width=2,class="floatedit",name="eks"}, {x=4,y=3,class="floatedit",name="wai"}, {x=0,y=4,width=5,class="label",label="Start / end / accel:"}, {x=1,y=5,width=2,class="edit",name="accel",value="0,0,1,"}, {x=4,y=5,class="checkbox",name="two",label="use next line's clip",hint="use clip from the next line (line will be deleted)"}, } buttons={"Transform","Calculate coordinates","Cancel"} repeat if P=="Calculate coordinates" then xx=res.eks yy=res.wai for key,v in ipairs(clipconfig) do if v.name=="klip" then v.value=round(cc1+xx,3)..","..round(cc2+yy,3)..","..round(cc3+xx,3)..","..round(cc4+yy,3) end if v.name=="accel" then v.value=res.accel end end end P,res=ADD(clipconfig,buttons,{ok='Transform',close='Cancel'}) if P=="Cancel" then ak() end until P~="Calculate coordinates" if P=="Transform" then newcoord=res.klip end

if res.two then nextline=subs[act+1] nextext=nextline.text if not nextext:match("\\i?clip%([%d%.%-]+,") then t_error("Error: second line must contain a rectangular clip.",1) else nextclip=nextext:match("\\i?clip%(([%d%.%-,]+)%)") text=text:gsub("^({\\[^}]*)}","%1\\t("..res.accel..ctype.."("..nextclip.."))}") end else text=text:gsub("^({\\[^}]*)}","%1\\t("..res.accel..ctype.."("..newcoord.."))}") end

text=text:gsub("0,0,1,\\","\\") line.text=text subs[act]=line if res.two then subs.delete(act+1) end end function clone(subs,sel) for z,i in ipairs(sel) do progress("Cloning... "..z.."/"..#sel) line=subs[i] text=line.text if not text:match("^{\\") then text=text:gsub("^","{\\clone}") end if res.cpos then if z==1 then posi=text:match("\\pos%(([^%)]-)%)") end if z>1 and text:match("\\pos") and posi then text=text:gsub("\\pos%b()","\\pos("..posi..")") end if z>1 and not text:match("\\pos") and not text:match("\\move") and posi and res.cre then text=text:gsub("^{\\","{\\pos%("..posi.."%)\\") end if z==1 then move=text:match("\\move%(([^%)]-)%)") end if z>1 and text:match("\\move") and move then text=text:gsub("\\move%b()","\\move("..move..")") end if z>1 and not text:match("\\move") and not text:match("\\pos") and move and res.cre then text=text:gsub("^{\\","{\\move("..move..")\\") end end

if res.corg then if z==1 then orig=text:match("\\org%(([^%)]-)%)") end if z>1 and orig then if text:match("\\org") then text=text:gsub("\\org%b()","\\org("..orig..")") elseif res.cre then text=text:gsub("^({\\[^}]*)}","%1\\org("..orig..")}") end end end

if res.copyrot then if z==1 then rotz=text:match("\\frz([%d%.%-]+)") rotx=text:match("\\frx([%d%.%-]+)") roty=text:match("\\fry([%d%.%-]+)") end if z>1 then if rotz then if res.cre then text=addtag3("\\frz"..rotz,text) else text=text:gsub("^({[^}]-\\frz)[%d%.%-]+","%1"..rotz) end end if rotx then if res.cre then text=addtag3("\\frx"..rotx,text) else text=text:gsub("^({[^}]-\\frx)[%d%.%-]+","%1"..rotx) end end if roty then if res.cre then text=addtag3("\\fry"..roty,text) else text=text:gsub("^({[^}]-\\fry)[%d%.%-]+","%1"..roty) end end end end

if res.cclip then -- line 1 - copy if z==1 then ik,klip=text:match("\\(i?)clip%(([^%)]-)%)") if klip and klip:match("m") then type1="vector" else type1="normal" end end -- lines 2+ - paste / replace if z>1 and text:match("\\i?clip") and klip then ik2,klip2=text:match("\\(i?)clip%(([^%)]-)%)") if res.klipmatch then kmatch=ik else kmatch=ik2 end if klip2:match("m") then type2="vector" klipv=klip2 else type2="normal" end if text:match("\\(i?)clip.-\\(i?)clip") then doubleclip=true ikv,klipv=text:match("\\(i?)clip%((%d?,?m[^%)]- )%)") else doubleclip=false end if res.combine and type1=="vector" and text:match("\\(i?clip)%(%d?,?m[^%)]-%)") then nklip=klipv.." "..klip else nklip=klip end -- 1 clip, stack if res.stack and type1~=type2 and not doubleclip then text=text:gsub("^({\\[^}]*)}","%1\\"..ik.."clip%("..nklip.."%)}") -- 2 clips -> not stack elseif doubleclip then if type1=="normal" then text=text:gsub("\\(i?clip)%([%d%.,%-]-%)","\\%1%("..nklip.."%)") end if type1=="vector" then text=text:gsub("\\(i?clip)%(%d?,?m[^%)]-%)","\\%1%("..nklip.."%)") end -- 1 clip, not stack elseif type1==type2 and not doubleclip or not res.stack and not doubleclip then text=text:gsub("\\i?clip%([^%)]-%)","\\"..kmatch.."clip%("..nklip.."%)") end end -- lines 2+ / paste / create if z>1 and not text:match("\\i?clip") and klip and res.cre then text=text:gsub("^({\\[^}]*)}","%1\\"..ik.."clip%("..klip.."%)}") end end

if res.ctclip then if z==1 then tklip=text:match("\\t%([%d%.,]*\\i?clip%(([^%)]-)%)") end if z>1 and text:match("\\i?clip") and tklip then text=text:gsub("\\t%(([%d%.,]*)\\(i?clip)%([^%)]-%)","\\t%(%1\\%2%("..tklip.."%)") end if z>1 and not text:match("\\t%([%d%.,]*\\i?clip") and tklip and res.cre then text=text:gsub("^({\\[^}]*)}","%1\\t%(\\clip%("..tklip.."%)%)}") end end

text=text:gsub("\\clone",""):gsub("{}","") line.text=text subs[i]=line end --posi,move,orig,klip,tklip=nil end function teleport(subs,sel) tpfx=0 tpfy=0 if res.tpmod then telemod={ {x=2,y=0,class="label",label=" Warped Teleportation"}, {x=2,y=1,class="floatedit",name="eggs",hint="X"}, {x=2,y=2,class="floatedit",name="why",hint="Y"}} press,rez=ADD(telemod,{"Warped Teleport","Disintegrate"},{close='Disintegrate'}) if press=="Disintegrate" then ak() end tpfx=rez.eggs tpfy=rez.why end for z,i in ipairs(sel) do progress("Teleporting... "..z.."/"..#sel) line=subs[i] text=line.text style=line.style xx=res.eks yy=res.wai fx=tpfx*(z-1) fy=tpfy*(z-1)

if res.tppos then if res.autopos and not text:match("\\pos") and not text:match("\\move") then text=getpos(subs,text) end text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return "\\pos("..a+xx+fx..","..b+yy+fy..")" end) end if res.tporg then text=text:gsub("\\org%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return "\\org("..a+xx+fx..","..b+yy+fy..")" end) end

if res.tpmov then text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) return "\\move("..a+xx+fx..","..b+yy+fy..","..c+xx+fx..","..d+yy+fy end) end

if res.tpclip then text=text:gsub("clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)",function(a,b,c,d) xd=xx+fx yd=yy+fy if res.tpexp then a=a-xd b=b-yd elseif res.tpc1 then a=a+xd b=b+yd end if res.tpexp or res.tpc2 then c=c+xd d=d+yd end return "clip("..a..","..b..","..c..","..d end)

if text:match("clip%(m [%d%a%s%-%.]+%)") then ctext=text:match("clip%(m ([%d%a%s%-%.]+)%)") ctext2=ctext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) return a+xx+fx.." "..b+yy+fy end) ctext=ctext:gsub("%-","%%-") text=text:gsub("clip%(m "..ctext,"clip(m "..ctext2) end

if text:match("clip%(%d+,m [%d%a%s%-%.]+%)") then fac,ctext=text:match("clip%((%d+),m ([%d%a%s%-%.]+)%)") factor=2^(fac-1) ctext2=ctext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) return a+factor*xx+fx.." "..b+factor*yy+fy end) ctext=ctext:gsub("%-","%%-") text=text:gsub(",m "..ctext,",m "..ctext2) end end

if res.tpmask then draw=text:match("}m ([^{]+)") draw2=draw:gsub("([%d%.%-]+) ([%d%.%-]+)",function(a,b) return round(a+xx+fx).." "..round(b+yy+fy) end) draw=esc(draw) text=text:gsub("(}m )"..draw,"%1"..draw2) end

text=roundpar(text,2) line.text=text subs[i]=line end end

-- reanimatools -- function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") val="[%d%-&]" if not tgt then tgt=tg:match("(\\%d?%a+)%b()") val="%b()" end if not tgt then tgt=tg:match("\\fn") val="" end if not tgt then t_error("adding tag '"..tg.."' failed.") end if tgt:match("clip") then txt,r=txt:gsub("^({[^}]-)\\i?clip%b()","%1"..tg) if r==0 then txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end elseif no_tf:match("^({[^}]-)"..tgt..val) then txt=txt:gsub("^({[^}]-)"..tgt..val.."[^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end function round4(a,b,c,d,dec) if not dec then dec=1 end a=math.floor(a*dec+0.5)/dec b=math.floor(b*dec+0.5)/dec c=math.floor(c*dec+0.5)/dec d=math.floor(d*dec+0.5)/dec return a,b,c,d end function roundpar(text,dec) text=text:gsub("(\\%a%a+)(%b())",function(a,b) return a..b:gsub("([%d%.%-]+)",function(c) return round(c,dec) end) end) return text end function getpos(subs,text) for g=1,#subs do if subs[g].class=="info" then local k=subs[g].key local v=subs[g].value if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if resx==nil then resx=0 end if resy==nil then resy=0 end if subs[g].class=="style" then local st=subs[g] if st.name==line.style then acleft=st.margin_l if line.margin_l>0 then acleft=line.margin_l end acright=st.margin_r if line.margin_r>0 then acright=line.margin_r end acvert=st.margin_t if line.margin_t>0 then acvert=line.margin_t end acalign=st.align if text:match("\\an%d") then acalign=text:match("\\an(%d)") end aligntop="789" alignbot="123" aligncent="456" alignleft="147" alignright="369" alignmid="258" if alignleft:match(acalign) then horz=acleft elseif alignright:match(acalign) then horz=resx-acright elseif alignmid:match(acalign) then horz=resx/2 end if aligntop:match(acalign) then vert=acvert elseif alignbot:match(acalign) then vert=resy-acvert elseif aligncent:match(acalign) then vert=resy/2 end break end end end if horz>0 and vert>0 then if not text:match("^{\\") then text="{\\rel}"..text end text=text:gsub("^({\\[^}]-)}","%1\\pos("..horz..","..vert..")}") :gsub("\\rel","") end return text end function gettimes(st,et) startf=ms2fr(st) endf=ms2fr(et) start2=fr2ms(startf) endt2=fr2ms(endf-1) tim=fr2ms(1) movt1=start2-st+tim movt2=endt2-st+tim return movt1,movt2 end function vfcheck() if aegisub.project_properties==nil then t_error("Current frame unknown.\nProbably your Aegisub is too old.\nMinimum required: r8374.",1) end vframe=aegisub.project_properties().video_position if vframe==nil or fr2ms(1)==nil then t_error("Current frame unknown. Probably no video loaded.",1) end if line then startf=ms2fr(line.start_time) start2=fr2ms(startf) vft=fr2ms(vframe) tim=math.floor((fr2ms(vframe+1)-vft)/2) videopos=vft-line.start_time+tim end end function detrack(z,sel,retrack,frame) if res.layers then for f=1,#retrack do if frame==retrack[f] then fpos=f end end total=#retrack else fpos=z total=#sel end return fpos,total end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function shiftsel(sel,i,mode) if ii then sel[s]=sel[s]+1 end end end if mode==1 then table.insert(sel,i+1) end table.sort(sel) return sel end function flip(rot,text) text=text:gsub("\\"..rot.."([%d%.%-]+)",function(r) r=tonumber(r) if r>0 then newrot=r-180 else newrot=r+180 end return "\\"..rot..newrot end) return text end function numgrad(V1,V2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc VC=round(acc_fac*(V2-V1)+V1,2) return VC end function acgrad(C1,C2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc B1,G1,R1=C1:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=C2:match("(%x%x)(%x%x)(%x%x)") A1=C1:match("(%x%x)") R1=R1 or A1 A2=C2:match("(%x%x)") R2=R2 or A2 nR1=(tonumber(R1,16)) nR2=(tonumber(R2,16)) R=acc_fac*(nR2-nR1)+nR1 R=tohex(round(R)) CC="&H"..R.."&" if B1 then nG1=(tonumber(G1,16)) nG2=(tonumber(G2,16)) nB1=(tonumber(B1,16)) nB2=(tonumber(B2,16)) G=acc_fac*(nG2-nG1)+nG1 B=acc_fac*(nB2-nB1)+nB1 G=tohex(round(G)) B=tohex(round(B)) CC="&H"..B..G..R.."&" end return CC end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function textmod(orig,text) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 vis=text:gsub("%b{}","") letrz=re.find(vis,".") for l=1,#letrz do table.insert(tk,letrz[l].str) end stags=text:match(STAG) or "" text=text:gsub(STAG,"") :gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-"..ATAG) do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-"..ATAG) do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext=stags..newline text=newtext:gsub("{}","") return text end function cleantr(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..trnsfrm.."}") return tags end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end function info(subs) for i=1,#subs do if subs[i].class=="info" then local k=subs[i].key local v=subs[i].value if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if subs[i].class=="dialogue" then break end end end

-- The Typesetter's Guide to the Hyperdimensional Relocator. intro=[[ Introduction

Hyperdimensional Relocator offers a plethora of functions, focusing primarily on \pos, \move, \org, \clip, and rotations. Anything related to positioning, movement, changing shape, etc., Relocator aims to make it happen.

]].."Current version: "..script_version.."\n\nUpdate location:\n"..script_url cannon=[[ 'Align X' means all selected \pos tags will have the same given X coordinate. Same with 'Align Y' for Y. Useful for aligning multiple signs horizontally/vertically or mocha signs that should move horizontally/vertically. 'by first' aligns by X or Y from the first line.

Horizontal Mirror: Duplicates the line and places it horizontally across the screen, mirrored around the middle. If you input a number, it will mirror around that coordinate instead, so if you have \pos(300,200) and input is 400, the mirrored result will be \pos(500,200). 'keep both' will keep the original line along with the mirrored one. 'rotate' will flip the text accordingly. Vertical Mirror is the logical vertical counterpart.

Shake: Apply to fbf lines with \pos tags to create a shaking effect. Input radius for how many pixels the sign may deflect from the original position. (Use Teleporter coordinates if you want different values for X and Y.) 'scaling' randomizes fscx/y. Value '6 'with fscx150 -> 144-156. (Uses input from the Force field.) 'rotate' adds shaking to \frz (pos input, degrees). 'smooth' will make shaking smoother. 'layers' will keep position/rotations the same for all layers. (Same value for same start time.)

Shake Rotation: Adds shaking effect to rotations. Degrees for frz from Repositioning Field, X/Y from Teleporter.

Shadow Layer: Creates shadow as a new layer. For offset, it uses in this order of priority: 1. value from Positron or Teleporter (xshad, yshad). 2. shadow value from the line. 3. shadow from style.

Space Out Letters: Set a distance, and line will be split into letters with that distance between them. Value 1 = regular distance (only split). You can randomly expect about 1% inaccuracy. With a rectangular clip, the script tries to fit the text from side to side of the clip. fscx is supported, fs isn't, nor are rotations, move, linebreaks, and other things. Inline tags should work. fbf X <--> Y: The fbf increase/decrease in pos X will become that of pos Y and vice versa. If 'by first' is checked, the first line is the reference line (won't change). Otherwise, it's the last line. With 'rotate' checked, the resulting direction is reversed. (Recalculator's Mirror adds more options to this.) Example: If a sign moves 100 pixels to the right, with 'first' it will move 100px down (or up with 'rotate'). Without 'first', the directions are the same, but sign will end where it did before, not start.]] clipthings=[[ Org to Fax: Calculates \fax from the line between \pos and \org coordinates.

Clip to Fax: Calculates \fax from the line between the first 2 points of a vectorial clip. Both of these work with \fscx, \fscy, and \frz. If the clip has 4 points, points 3-4 are used to calculate fax for the last character, and a gradient is applied. See blog post for more info - http://unanimated.xtreemhost.com/itw/tsblok.htm#fax

Clip to Frz: Calculates \frz from the first 2 points of a vectorial clip. Direction of text is from point 1 to point 2. If the clip has 4 points, the frz is average from 1-2 and 3-4. (Both lines must be in the same direction.) This helps when the sign is in the middle of a rectangular area where the top and bottom lines converge.

Clip to Reposition: Shifts \pos based on first 2 points of a vectorial clip. You can use a reference point in the video image and set the clip points at start/end of the movement. This may help when you want to "track" 2-3 frames and don't want to open Mocha for that.]] replika=[[ Create replicas of selected lines positioned over specified distances. 'Replicas' is how many replicas of each line you'll make. Distances for 'one replica': sets distances between two consecutive lines. In this mode, distances are always relative, and formation curve is 1. Distances for 'last replica': distance/coordinates for last replica. 'relative' distance is from the original. 'absolute' distance is video coordinates. (This way all selected lines can replicate towards the same point.) 'formation curve' is acceleration for X and Y, if 'last replica' selected. 'use \move for last replica coordinates': target coordinates are taken from \move tag for each line. This overrides 'one replica' mode. All selected lines must have a \move tag. If not checked, lines can still have \move, and all coordinates will be shifted. 'Delay' is by how many frames each replica will be shifted, if you want them to appear one by one over time. 'keep end' will keep end time for such replicas same as the original. Otherwise it gets shifted along with start time. Should the end time be lower than start time, duration will be 1 frame. (You should prevent this from happening as it makes no sense.)]] fbfretrack=[[ There is simple mode and 'smoothen track' mode. Simple mode is like fbf-transform for position, but it can be applied to several layers at the same time and have different accel for X and Y. If you check 'layers', the scale is not by selection but by start time. You can have subtitles sorted by layers, but each layer must be sorted by time. Of course each frame will have the same position for all layers/signs. You can use Repositioning Field for accel, or Teleporter for separate X/Y accel. Without 'layers' checked, it simply goes through selected lines and can be applied to signs in the same frame.

'Smoothen track' mode is activated when you check 'smooth'. This is designed for smoothening tracking data, i.e. it will move positions that stand out closer to the main line of the track. It would make no sense to apply this to shaking signs, but if you have trouble tracking something in mocha and the sign tends to jump off on some frames, this will pull the jumps back in line. You can apply different strength of smoothening, by using the Force Field. 0 is the lowest strength; 100 is the highest and will make the track a straight line.]] travel=[[ 'Horizontal' move means Y2 will be the same as Y1 so that the sign moves in a straight horizontal manner. Same principle for 'vertical.'

Multimove: When first line has \move and the others have \pos, \move for them is calculated from the first one.

Clip2move*: Move is calculated from first 2 points of a vectorial clip. Works for \pos, or adjusts \move.

Shiftmove*: Like teleporter, but only for the 2nd set of coordinates, ie. X2 and Y2. Uses input from Teleporter. 'current frame' sets -end- timecode to current frame

Shiftstart: Similarly, this only shifts the initial \move coordinates. 'current frame' sets -start- timecode to current frame

Reverse Move: Switches the coordinates, reversing the movement direction.

Move to*: Teleporter input sets target coordinats for \move for all selected lines. 'current frame' sets end timecode to current frame

Move Clip: Moves regular clip along with \move using \t\clip.

*'times' will add timecodes to \move for these functions]] transmoo=[[ Transmove* Main function: create \move from two lines with \pos. Duplicate your line, and position the second one where you want the \move the end. Script will create \move from the two positions. Second line will be deleted by default; it's there just so you can comfortably set the final position. Extra function: to make this a lot more awesome, this can create transforms. The second line is used not only for \move coordinates but also for transforms. Any tag on line 2 that's different from line 1 will be used to create a transform on line 1. So for a \move with transforms, you can set the initial sign and then the final sign while everything is static. You can time line 2 to just the last frame. The script only uses timecodes from line 1. Text from line 2 is also ignored (assumed to be same as line 1). You can time line 2 to start after line 1 and check 'keep both.' That way line 1 transforms into line 2, and the sign stays like that for the duration of line 2. 'Rotation acceleration' - like with fbf-transform, this ensures that transforms of rotations will go the shortest way, thus going only 4 degrees from 358 to 2 and not 356 degrees around. If the \pos is the same on both lines, only transforms will be applied. Logically, you must NOT select 2 consecutive lines when you want to run this, though you can select every other line.

'times' will add timecodes to \move]] morph=[[ Round Numbers: rounds coordinates for pos, move, org and clip depending on the 'Round' submenu.

Joinfbflines: Select frame-by-frame lines, input number X into Force Field, and each X lines will be joined. (same way as with 'Join (keep first)' from the right-click menu)

KillMoveTimes: nukes timecodes from a \move tag. KillTransTimes: nukes timecodes from \t tags. FullMoveTimes: sets timecodes for \move to the first and last frame. FullTransTimes: sets timecodes for \t to the first and last frame.

Move V. Clip: Moves vectorial clip on fbf lines based on \pos tags. Note: For decimals on v-clip coordinates: xy-vsfilter OK; libass rounds them; regular vsfilter fails completely.

Set Origin: set \org based off of \pos using Teleporter coordinates. (Shifts it by that much from \pos.)

Set Rotation: adds selected rotation tags with the value from the 'rotation' menu. (You can get multiples of 30 using the Aegisub tool while holding Ctrl.)

Rotate 180: rotates text by 180 degrees from current values of selected rotations (frx, fry, frz).

Negative rot: keeps the same rotation, but changes to negative number (350 -> -10), which helps with transforms.

Vector2rect/Rect.2vector: converts between rectangular and vectorial clips.

Clip Scale: Use Force field to set the X factor in "clip(X,m ", and the clip will be recalculated accordingly.

Find Centre: A useless function that sets \pos in the centre of a rectangular clip.

Randomize: randomizes values of given tags. With \fs50 and value 4 you can get fs 46-54. For regular tags, you can input multiple ones with commas between them.

Letterbreak: creates vertical text by putting a linebreak after each letter. Wordbreak: replaces spaces with linebreaks.]] morph2fbf=[[ Line2fbf:

Splits a line frame by frame, ie. makes a line for each frame. If there's \move, it calculates \pos tags for each line. If there are transforms, it calculates values for each line. It should deal with all transforms, including inline tags and acceleration. Move and transforms can have timecodes. (Though some calculations may end up about 1% off.) \fad is supported too, but may not be entirely accurate with complex alphas. Very complex lines with multiple transforms are likely to break in some way.

'clip2fbf' - clips will be shifted along with \move. (Don't use with clip transforms.)]] morphorg=[[ Calculate Origin:

This calculates \org from a tetragonal vectorial clip you draw. Draw a vectorial clip with 4 points, aligned to a surface you need to put your sign on. The script will calculate the vanishing points for X and Y and give you \org. Make the clip as large as you can, since on a smaller one any inaccuracies will be more obvious. If you draw it well enough, the accuracy of the \org point should be pretty decent. (It won't work when both points on one side are lower than both points on the other side.) See this blog post for more details: http://unanimated.xtreemhost.com/itw/tsblok.htm#origin ]] morphclip=[[ Transform Clip:

Go from \clip(x1,y1,x2,y2) to \clip(x1,y1,x2,y2)\t(\clip(x3,y3,x4,y4)). Coordinates are read from the line. You can set by how much x and y should change, and new coordinates will be calculated.

'use next line's clip' allows you to use clip from the next line. Create a line after your current one (or just duplicate), set the clip you want to transform to on it, and check 'use next line's clip'. The clip from the next line will be used for the transform, and the line will be deleted.]] morphmasks=[[ Extend Mask: Use Teleporter X and Y fields to extend a mask in either or both directions. This is mainly intended to easily convert something like a rounded square to another rounded rectangle. Works optimally with a 0,0 coordinate in the centre. May do weird things with curves. When all coordinates are to one side from 0,0, then this works like shifting.

Flip mask: Flips a mask so that when used with its non-flipped counterpart, they create hollow space. For example you have a rounded square. Duplicate it, extend one by 10 pixels in each direction, flip it, and then merge them. You'll get a 10 px outline.

Adjust Drawing: (You must not have an unrelated clip in the line.) 1. Creates a clip that copies the drawing. 2. You adjust points with clip tool. 3. Applies new coordinates to the drawing.

Randomask: Moves points in a drawing, each in a random direction, by a factor taken from the Force field.]] cloan=[[ This copies specified tags from first line to the others. Options are position, move, origin point, clip, and rotations. replicate missing tags: creates tags if they're not present stack clips: allows stacking of 1 normal and 1 vector clip in one line match type: if current clip/iclip doesn't match the first line, it will be switched to match combine vectors: if the first line has a vector clip, then for all other lines with vector clips the vectors will be combined into 1 clip copy rotations: copies all rotations]] port=[[ Teleport shifts coordinates for selected tags (\pos\move\org\clip) by given X and Y values. It's a simple but powerful tool that allows you to move whole gradients, mocha-tracked signs, etc.

Note that the Teleporter fields are also used for some other functions, like Shiftstart and Shiftmove. These functions don't use the 'Teleportation' button but the one for whatever part of HR they belong to.

'mod' allows you to add an extra factor applied line by line. For example if you set '5' for 'X', things will shift by extra 5 pixels for each new line. c1 and c2 control whether Teleportation affects top left and/or bottom right corner of a rectangular clip. This means that for both X and Y, you can move either one or both sides of the clip.

'exp' means a rectangular clip won't be moved but expanded. X 10 means the clip will expand by 10 to the left and right. This ignores the c1/c2 settings.]] function guide() stg_top={x=0,y=0,class="label", label="The Typesetter's Guide to the Hyperdimensional Relocator. "} stg_toptop={x=1,y=0,class="label",label="Choose topic below."} stg_topos={x=1,y=0,class="label",label=" Repositioning Field"} stg_toptra={x=1,y=0,class="label",label=" Soul Bilocator"} stg_toporph={x=1,y=0,class="label",label=" Morphing Grounds"} stg_topseq={x=1,y=0,class="label",label=" Cloning Laboratory"} stg_toport={x=1,y=0,class="label",label=" Teleportation"} stg_intro={x=0,y=1,width=2,height=9,class="textbox",name="gd",value=intro} stg_cannon={x=0,y=1,width=2,height=19,class="textbox",name="gd",value=cannon} stg_can_to_things={x=0,y=1,width=2,height=10,class="textbox",name="gd",value=clipthings} stg_replicate={x=0,y=1,width=2,height=14,class="textbox",name="gd",value=replika} stg_retrack={x=0,y=1,width=2,height=12,class="textbox",name="gd",value=fbfretrack} stg_travel={x=0,y=1,width=2,height=13,class="textbox",name="gd",value=travel} stg_transmove={x=0,y=1,width=2,height=13,class="textbox",name="gd",value=transmoo} stg_morph={x=0,y=1,width=2,height=20,class="textbox",name="gd",value=morph} stg_morph2fbf={x=0,y=1,width=2,height=8,class="textbox",name="gd",value=morph2fbf} stg_morphorg={x=0,y=1,width=2,height=8,class="textbox",name="gd",value=morphorg} stg_morphclip={x=0,y=1,width=2,height=8,class="textbox",name="gd",value=morphclip} stg_morpmsk={x=0,y=1,width=2,height=10,class="textbox",name="gd",value=morphmasks} stg_cloan={x=0,y=1,width=2,height=9,class="textbox",name="gd",value=cloan} stg_port={x=0,y=1,width=2,height=9,class="textbox",name="gd",value=port} cp_main={"Positron Cannon","Hyperspace Travel","Metamorphosis","Cloning Sequence","Teleportation","Disintegrate"} cp_back={"Warp Back"} cp_cannon={"Warp Back","Positron Cannon","org/clip to Things","Replicate","FBF Retrack"} cp_travel={"Warp Back","Hyperspace Travel","Transmove"} cp_morph={"Warp Back","Metamorphosis","Line2fbf","Calculate Origin","Transform Clip","Masks/drawings"} esk1={close='Disintegrate'} esk2={cancel='Warp Back'} stg={stg_top,stg_toptop,stg_intro} control_panel=cp_main esk=esk1 repeat stg={stg_top,stg_toptop,stg_intro} control_panel=cp_main esk=esk1 if press=="Positron Cannon" then stg={stg_top,stg_topos,stg_cannon} control_panel=cp_cannon esk=esk2 end if press=="Hyperspace Travel" then stg={stg_top,stg_toptra,stg_travel} control_panel=cp_travel esk=esk2 end if press=="Metamorphosis" then stg={stg_top,stg_toporph,stg_morph} control_panel=cp_morph esk=esk2 end if press=="Cloning Sequence" then stg={stg_top,stg_topseq,stg_cloan} control_panel=cp_back esk=esk2 end if press=="Teleportation" then stg={stg_top,stg_toport,stg_port} control_panel=cp_back esk=esk2 end if press=="org/clip to Things" then stg={stg_top,stg_topos,stg_can_to_things} control_panel=cp_cannon esk=esk2 end if press=="Replicate" then stg={stg_top,stg_topos,stg_replicate} control_panel=cp_cannon esk=esk2 end if press=="FBF Retrack" then stg={stg_top,stg_topos,stg_retrack} control_panel=cp_cannon esk=esk2 end if press=="Transmove" then stg={stg_top,stg_topos,stg_transmove} control_panel=cp_travel esk=esk2 end if press=="Line2fbf" then stg={stg_top,stg_toporph,stg_morph2fbf} control_panel=cp_morph esk=esk2 end if press=="Calculate Origin" then stg={stg_top,stg_toporph,stg_morphorg} control_panel=cp_morph esk=esk2 end if press=="Transform Clip" then stg={stg_top,stg_toporph,stg_morphclip} control_panel=cp_morph esk=esk2 end if press=="Masks/drawings" then stg={stg_top,stg_toporph,stg_morpmsk} control_panel=cp_morph esk=esk2 end if press=="Warp Back" then stg={stg_top,stg_toptop,stg_intro} control_panel=cp_main esk=esk1 end press,rez=ADD(stg,control_panel,esk) until press=="Disintegrate" if press=="Disintegrate" then ak() end end

-- Config Stuff -- function saveconfig() hrconf="Hyperconfigator\n\n" for key,val in ipairs(hyperconfig) do if val.class=="floatedit" or val.class=="dropdown" then hrconf=hrconf..val.name..":"..res[val.name].."\n" end if val.class=="checkbox" and val.name~="save" then hrconf=hrconf..val.name..":"..tf(res[val.name]).."\n" end end hyperkonfig=ADP("?user").."\\relocator.conf" file=io.open(hyperkonfig,"w") file:write(hrconf) file:close() ADD({{class="label",label="Config saved to:\n"..hyperkonfig}},{"OK"},{close='OK'}) end function loadconfig() rconfig=ADP("?user").."\\relocator.conf" file=io.open(rconfig) if file~=nil then konf=file:read("*all") konf=konf:gsub("%-frz%-","rotation") io.close(file) for key,val in ipairs(hyperconfig) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="dropdown" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function relocator(subs,sel,act) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame keyframes=aegisub.keyframes() ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" rin=subs[act] tk=rin.text if tk:match"\\move%(" then m1,m2,m3,m4=tk:match("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)") M1=m3-m1 M2=m4- m2 mlbl=" mov: "..M1..","..M2 else mlbl="" end

Repositioning={"Align X","Align Y","org to fax","clip to fax","clip to frz","clip to reposition","horizontal mirror","vertical mirror","shake","shake rotation","shadow layer","space out letters","fbf X <--> Y","replicate","fbf retrack"} Bilocator={"transmove","horizontal","vertical","multimove","clip2move","rvrs. move","shiftstart","shiftmove","move to","move clip","randomove"} Morphing={"round numbers","line2fbf","join fbf lines","killmovetimes","killtranstimes","fullmovetimes","fulltranstimes","move v. clip","set origin","calculate origin","transform clip","set rotation","rotate 180","negative rot","vector2rect.","rect.2vector","clip scale","find centre","extend mask","flip mask","adjust drawing","randomask","randomize...","letterbreak","wordbreak"} Rounding={"all","pos","move","org","clip","mask"} Freezing={"rotation","5","10","20","45","70","110","135","160","-5","-10","-20","-45","-70","-110","-135","-160"} noneg="\\bord\\shad\\xbord\\ybord\\fs\\blur\\be\\fscx\\fscy" hyperconfig={ {x=12,y=0,width=2,class="label",label="Teleportation"}, {x=12,y=1,width=3,class="floatedit",name="eks",hint="X"}, {x=12,y=2,width=3,class="floatedit",name="wai",hint="Y"}, {x=0,y=0,width=3,class="label",label="Repositioning Field"}, {x=0,y=1,width=2,class="dropdown",name="posi",value="clip to frz",items=Repositioning}, {x=0,y=2,width=2,class="floatedit",name="post",value=0}, {x=0,y=3,class="checkbox",name="first",label="by first",value=true,hint="align with first line"}, {x=1,y=3,class="checkbox",name="rota",label="rotate",hint="shake rotation / rotate mirrors"}, {x=0,y=4,class="checkbox",name="layers",label="layers",value=true,hint="synchronize shaking for all layers"}, {x=1,y=4,class="checkbox",name="smo",label="smooth",hint="smoothen shaking"}, {x=1,y=5,class="checkbox",name="sca",label="scaling",hint="add scaling to shake"}, {x=0,y=6,class="label",label=" Force:"}, {x=1,y=6,width=3,class="floatedit",name="force",value=0,hint="shake: scaling value\nfbf retrack: smoothening force\njoin fbf lines: # of lines\nclip scale: scale factor\nrandomask: randomness factor"},

{x=3,y=0,width=3,class="label",label="Soul Bilocator"}, {x=3,y=1,width=2,class="dropdown",name="move",value="transmove",items=Bilocator}, {x=3,y=2,width=2,class="checkbox",name="keep",label="keep both",hint="keeps both lines for transmove/mirrors"}, {x=3,y=3,width=4,class="checkbox",name="rotac",label="rotation acceleration",value=true,hint="transmove option"}, {x=3,y=4,width=1,class="checkbox",name="times",label="times",hint="set \\move times"}, {x=3,y=5,width=3,class="checkbox",name="videofr",label="current frame",hint="set relevant timecode\nto current frame\n(shiftstart, shiftmove)"},

{x=6,y=0,width=2,class="label",label="Morphing Grounds"}, {x=6,y=1,width=2,class="dropdown",name="mod",value="round numbers",items=Morphing}, {x=6,y=2,class="label",label="round:"}, {x=7,y=2,class="dropdown",name="rnd",items=Rounding,value="all"}, {x=7,y=3,class="dropdown",name="rndec",items={"1","0.1","0.01","0.001"},value="1",hint="rounding"}, {x=7,y=4,class="dropdown",name="freeze",items=Freezing,value="rotation"}, {x=6,y=6,width=2,class="checkbox",name="delfbf",label="delete orig. line",value=true,hint="delete original line for line2fbf"}, {x=7,y=7,width=1,class="checkbox",name="c2fbf",label="clip2fbf",value=true,hint="line2fbf: shift clip along with \\move"}, {x=6,y=4,width=1,class="checkbox",name="frz",label="frz",value=true,hint=""}, {x=6,y=5,width=1,class="checkbox",name="frx",label="frx",hint=""}, {x=7,y=5,width=1,class="checkbox",name="fry",label="fry",hint=""},

{x=8,y=0,width=3,class="label",label="Cloning Laboratory"}, {x=8,y=1,width=2,class="checkbox",name="cpos",label="\\posimove",value=true}, {x=10,y=1,class="checkbox",name="corg",label="\\org",value=true}, {x=8,y=2,class="checkbox",name="cclip",label="\\[i]clip",value=true}, {x=9,y=2,width=2,class="checkbox",name="ctclip",label="\\t(\\[i]clip)",value=true}, {x=8,y=6,width=4,class="checkbox",name="cre",label="replicate missing tags",value=true}, {x=8,y=3,width=2,class="checkbox",name="stack",label="stack clips"}, {x=8,y=5,width=3,class="checkbox",name="copyrot",label="copy rotations"}, {x=10,y=3,width=3,class="checkbox",name="klipmatch",label="match type "}, {x=8,y=4,width=3,class="checkbox",name="combine",label="combine vectors"},

{x=13,y=3,class="checkbox",name="tppos",label="pos",value=true}, {x=13,y=4,class="checkbox",name="tpmov",label="move",value=true}, {x=14,y=3,class="checkbox",name="tporg",label="org",value=true}, {x=14,y=4,class="checkbox",name="tpclip",label="clip",value=true}, {x=12,y=4,class="checkbox",name="tpc1",label="c1",value=true,hint="affect top left corner of rectangular clip"}, {x=12,y=5,class="checkbox",name="tpc2",label="c2",value=true,hint="affect bottom right corner of rectangular clip"}, {x=14,y=5,class="checkbox",name="tpexp",label="exp",hint="expand rectangular clip in opposite directions"}, {x=13,y=5,class="checkbox",name="tpmask",label="mask"}, {x=14,y=0,class="checkbox",name="tpmod",label="mod"}, {x=12,y=6,width=3,class="checkbox",name="autopos",label="pos with tags missing",value=true,hint="Teleport position when \\pos tags missing"},

{x=0,y=7,width=3,class="checkbox",name="space",label="SpaceTravel Guide", hint="The Typesetter's Guide to the Hyperdimensional Relocator."}, {x=3,y=7,width=4,class="label",name="moo",label=mlbl}, {x=8,y=7,width=2,class="checkbox",name="rpt",label="Repeat",hint="Repeat with last settings (any function)"}, {x=10,y=7,width=3,class="checkbox",name="save",label="Save config",hint="Save current configuration"}, {x=13,y=7,width=2,class="label",label=""} } loadconfig() if remember then for key,val in ipairs(hyperconfig) do if val.name=="posi" then val.value=lastpos end if val.name=="move" then val.value=lastmove end if val.name=="mod" then val.value=lastmod end end end P,res=ADD(hyperconfig, {"Positron Cannon","Hyperspace Travel","Metamorphosis","Cloning Sequence","Teleportation","Disintegrate"},{cancel='Disintegrate'}) if P=="Disintegrate" then ak() end

if imprint and res.rpt then res=imprint end remember=true imprint=res lastpos=res.posi lastmove=res.move lastmod=res.mod if res.save then saveconfig() ak() end

if P=="Positron Cannon" then if res.space then guide(subs,sel) else sel=positron(subs,sel) end end if P=="Hyperspace Travel" then if res.move=="multimove" then multimove (subs,sel) elseif res.move=="randomove" then randomove (subs,sel) else bilocator(subs,sel) end end if P=="Metamorphosis" then aegisub.progress.title(string.format("Morphing...")) if res.mod=="line2fbf" then sel=movetofbf(subs,sel) elseif res.mod=="transform clip" then transclip(subs,sel,act) elseif res.mod=="join fbf lines" then joinfbflines(subs,sel) else modifier(subs,sel) end end if P=="Cloning Sequence" then clone(subs,sel) end if P=="Teleportation" then teleport(subs,sel) end aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(relocator) else aegisub.register_macro(script_name,script_description,relocator) end -- Example: Set to 120%, check fscx and fscy, and all values for fscx/y will be increased by 20% for selected lines. -- With "multiply/add more with each line" and fscx100 you'll get 120, 140, 160, 180 for consecutive lines. -- Alternative 2nd value allows for a different value for all Y things (fscY, Ybord, Yshad, frY, faY, all Y coordinates) + fad2, t2. -- It will be used as Multiply or Add depending on the button you press. -- Mirror: intended for mirroring mocha data. Applied to fbf lines with pos going from 200 to 260, it will go from 200 to 140. -- Works with position, origin, rotations, and rectangular clip. If clip changes size/shape, results will be weird. -- Also works with move (though that makes pretty much no sense to use) and fax/fay. -- Regradient: if you check a tag that appears at least 3 times in the line, the middle values are calculated as gradient from the first and last. -- That means that if you have an existing gradient and change the values on either end, the gradient is recalculated from those. -- Works for the first 3 rows of tags except kara and for alpha/colours. -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#recalculator script_name="Recalculator" script_description="recalculates things" script_author="unanimated" script_version="3.0" script_namespace="ua.Recalculator" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end

-- SETTINGS: type the names of checkboxes you want checked by default as you see them in the GUI, separated by commas. checked="fscx,fscy,anchor clip" default_rounding=2

-- END OF SETTINGS re=require'aegisub.re' function calc(num) if P=="Multiply" then num=round(num+num*count,rnd) end if P=="Add" then num=round(num+(res.add*linec),rnd)end if neg==0 and num<0 then num=0 end return num end function calc2(num) if P=="Multiply" then num=round(num+num*altcount,rnd) end if P=="Add" then num=round(num+(alt*linec),rnd)end if neg==0 and num<0 then num=0 end return num end function recalc(text,tg,c) if not res.regtag and not res.tftag then t_error("For "..tg..", you must select 'regular tags' or 'tags in transforms' or both.",1) end if c==1 then kalk=calc else kalk=calc2 end if neg==1 then val="([%d%.%-]+)" else val="([%d%.]+)" end -- split into non-tf/tf segments if there are transforms seg={} if text:match("\\t%b()") then for seg1,seg2 in text:gmatch("(.-)(\\t%b())") do table.insert(seg,seg1) table.insert(seg,seg2) end table.insert(seg,text:match("^.*\\t%b()(.-)$")) else table.insert(seg,text) end -- change non-tf/tf/all segments for q=1,#seg do if res.regtag and not seg[q]:match("\\t%b()") then seg[q]=seg[q]:gsub(tg.."("..val..")",function(a) return tg..kalk(tonumber(a)) end) end if res.tftag and seg[q]:match("\\t%b()") then seg[q]=seg[q]:gsub(tg.."("..val..")",function(a) return tg..kalk(tonumber(a)) end) end end nt="" for q=1,#seg do nt=nt..seg[q] end return nt end function recalc2(text,tg,c) if neg==1 then val="([%d%.%-]+)" else val="([%d%.]+)" end if c==1 then text=text:gsub(tg.."%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return tg.."("..calc(tonumber(a))..","..b..")" end) else text=text:gsub(tg.."%(([%d%.%-]+),([%d%.%-]+)%)",function(a,b) return tg.."("..a..","..calc2(tonumber(b))..")" end) end return text end function multiply(subs,sel) c=(res.pc-100)/100 oc=res.pc/100 rnd=res.rnd if res.alt then ac=res.altval/100 else ac=oc end if P=="Multiply" then alt=(res.altval-100)/100 else alt=res.altval end if res.mov1 or res.mov2 or res.mov3 or res.mov4 then move=1 else move=0 end if res.clipx or res.clipy then clip=1 else clip=0 end for z,i in ipairs(sel) do progress("Processing line: "..z.."/"..#sel) if res.byline then count=z*c linec=z altcount=z*alt else count=c linec=1 altcount=alt end if not res.alt then altcount=count alt=res.add end line=subs[i] text=line.text sr=stylechk(line.style) if not text:match("^{\\") then text="{\\rec}"..text end notf=text:gsub("\\t%b()","") spac=sr.spacing if res.fsp and not notf:match("\\fsp") and spac~=0 then text=addtag3("\\fsp"..spac,text) end fsize=sr.fontsize if res.fs and not notf:match("\\fs%d") then text=addtag3("\\fs"..fsize,text) end scy=sr.scale_y if res.fscy and not notf:match("\\fscy") then text=addtag3("\\fscy"..scy,text) end scx=sr.scale_x if res.fscx and not notf:match("\\fscx") then text=addtag3("\\fscx"..scx,text) end shdw=sr.shadow if res.shad and not notf:match("\\shad") and shdw~=0 then text=addtag3("\\shad"..shdw,text) end brdr=sr.outline if res.bord and not notf:match("\\bord") and brdr~=0 then text=addtag3("\\bord"..brdr,text) end

if res.fscx then neg=0 text=recalc(text,"\\fscx",1) end if res.fscy then neg=0 text=recalc(text,"\\fscy",2) end if res.fs then neg=0 text=recalc(text,"\\fs",1) end if res.fsp then neg=1 text=recalc(text,"\\fsp",1) end if res.bord then neg=0 text=recalc(text,"\\bord",1) end if res.shad then neg=0 text=recalc(text,"\\shad",1) end if res.blur then neg=0 text=recalc(text,"\\blur",1) end if res.be then neg=0 text=recalc(text,"\\be",1) end if res.xbord then neg=0 text=recalc(text,"\\xbord",1) end if res.ybord then neg=0 text=recalc(text,"\\ybord",2) end if res.xshad then neg=1 text=recalc(text,"\\xshad",1) end if res.yshad then neg=1 text=recalc(text,"\\yshad",2) end if res.frx then neg=1 text=recalc(text,"\\frx",1) end if res.fry then neg=1 text=recalc(text,"\\fry",2) end if res.frz then neg=1 text=recalc(text,"\\frz",1) end if res.fax then neg=1 text=recalc(text,"\\fax",1) end if res.fay then neg=1 text=recalc(text,"\\fay",2) end

if res.kara then neg=0 text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("^({[^}]-\\[Kk][fo]?)([%d%.]+)",function(a,b) return a..calc(tonumber(b)) end) end

if res.posx then neg=1 text=recalc2(text,"\\pos",1) end if res.posy then neg=1 text=recalc2(text,"\\pos",2) end

if res.orgx then neg=1 text=recalc2(text,"\\org",1) end if res.orgy then neg=1 text=recalc2(text,"\\org",2) end

if res.fad1 then neg=0 text=recalc2(text,"\\fad",1) end if res.fad2 then neg=0 text=recalc2(text,"\\fad",2) end

if move==1 then neg=1 text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%- ]+)",function(a,b,c,d) if res.mov1 then a=calc(tonumber(a)) end if res.mov2 then b=calc2(tonumber(b)) end if res.mov3 then c=calc(tonumber(c)) end if res.mov4 then d=calc2(tonumber(d)) end return "\\move("..a..","..b..","..c..","..d end) end

if clip==1 then neg=1 if not res.regtag and not res.tftag then t_error("You must select 'regular tags' or 'tags in transforms' or both.",true) end orig=text if res.anchor and P=="Multiply" then m=1/oc m2=1/ac text=text:gsub("clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) x=0 y=0 if res.clipx then x=(a+c)/2-((a+c)/2)*m end if res.clipy then y=(b+d)/2-((b+d)/2)*m2 end return "clip("..a-x..","..b-y..","..c-x..","..d-y end) if text:match("clip%(m [%d%a%s%-%.]+%)") then ctext=text:match("clip%(m ([%d%a%s%-%.]+)%)") c1,c2=ctext:match("([%d%-%.]+)%s([%d%-%.]+)") x=0 y=0 if res.clipx then x=c1-c1*m end if res.clipy then y=c2-c2*m2 end ctext2=ctext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) return a-x.." "..b-y end) ctext=ctext:gsub("%-","%%-") text=text:gsub("clip%(m "..ctext,"clip(m "..ctext2) end end text=text:gsub("clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) if res.clipx then a=calc(tonumber(a)) c=calc(tonumber(c)) end if res.clipy then b=calc2(tonumber(b)) d=calc2(tonumber(d)) end return "clip("..a..","..b..","..c..","..d..")" end) if text:match("clip%(m [%d%a%s%-%.]+%)") then ctext=text:match("clip%(m ([%d%a%s%-%.]+)%)") ctext2=ctext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) if res.clipx then a=calc(tonumber(a)) end if res.clipy then b=calc2(tonumber(b)) end return a.." "..b end) ctext=ctext:gsub("%-","%%-") text=text:gsub(ctext,ctext2) end -- regular vs transforms: shitty, slow workaround because fuck if i'm gonna figure out what's going on above origami={} hybrid={} textn="" if text:match("\\t%b()") then -- transforms present if not res.regtag or not res.tftag then for seg1,seg2 in orig:gmatch("(.-)(\\t%b())") do table.insert(origami,seg1) table.insert(origami,seg2) end table.insert(origami,orig:match("^.*\\t%b()(.-)$")) for seg1,seg2 in text:gmatch("(.-)(\\t%b())") do table.insert(hybrid,seg1) table.insert(hybrid,seg2) end table.insert(hybrid,text:match("^.*\\t%b()(.-)$")) for q=1,#hybrid do if hybrid[q]:match("\\t") then if res.regtag then textn=textn..origami[q] else textn=textn..hybrid[q] end else if res.tftag then textn=textn..origami[q] else textn=textn..hybrid[q] end end end text=textn end else -- no transforms present - only regular clips recalculated if not res.regtag then text=orig end -- yep, it was all for nothing end end if res.drawx or res.drawy then neg=1 if text:match("\\p[1-9]") and text:match("}m [%d%a%s%-%.]+") then dtext=text:match("}m ([%d%a%s%-%.]+)") dtext2=dtext:gsub("([%d%-%.]+)%s([%d%-%.]+)",function(a,b) if res.drawx then xx=math.floor(calc(tonumber(a))+0.5) else xx=a end if res.drawy then yy=math.floor(calc2(tonumber(b))+0.5) else yy=b end return xx.." "..yy end) dtext=dtext:gsub("%-","%%-") text=text:gsub(dtext,dtext2) end end

if res.ttim1 then neg=1 text=text:gsub("\\t%(([%d%.%-]+),([%d%.%-]+),",function(a,b) return "\\t("..calc(tonumber(a))..","..b.."," end) end if res.ttim2 then neg=1 text=text:gsub("\\t%(([%d%.%-]+),([%d%.%-]+),",function(a,b) return "\\t("..a..","..calc2(tonumber(b)).."," end) end

text=text:gsub("\\rec","") line.text=text subs[i]=line end end function regrad(subs,sel) gradlist={"bord","shad","blur","be","fs","fscx","fscy","fsp","frx","fry","frz","fax","fay","xshad","xbord","yshad","ybor d"} acol={"alpha","1a","2a","3a","4a","1c","2c","3c","4c"} rnd=res.rnd for z=1,#sel do i=sel[z] progress("Processing line: "..z.."/"..#sel) line=subs[i] text=line.text -- regular tags for x=1,#gradlist do tag=gradlist[x] _,c=text:gsub("\\"..tag.."%-?%d","") if res[tag] and c>2 then text=text:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) tagtab={} for tt in text:gmatch(".-\\"..tag.."[%d%.%-]+") do table.insert(tagtab,tt) end count=#tagtab if res.space then spaceworks("{\\"..tag.."0fake} ","[%d%.%-]+") end END=text:match("^.*\\"..tag.."[%d%.%-]+(.-)$") val1=tonumber(tagtab[1]:match("\\"..tag.."([%d%.%-]+)")) val2=tonumber(tagtab[count]:match("\\"..tag.."([%d%.%-]+)")) for t=2,count-1 do valc=currentval(val1,val2,t) valc=round(valc,rnd) tagtab[t]=tagtab[t]:gsub("(\\"..tag..")([%d%.%-]+)","%1"..valc) end nt=END for a=count,1,-1 do nt=tagtab[a]..nt end nt=nt:gsub("{\\%a%a+[%d%.%-]+fake}","") text=nt:gsub("|t%b()",function(t) return t:gsub("|","\\") end) end end -- colours text=text:gsub("\\c&","\\1c&") for x=1,#acol do tag=acol[x] _,c=text:gsub("\\"..tag.."&","") if res[tag] and c>2 then text=text:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) tagtab={} for tt in text:gmatch(".-\\"..tag.."&H%x+&") do table.insert(tagtab,tt) end count=#tagtab if res.space and tag:match("a") then spaceworks("{\\"..tag.."&H00&fake} ","&H%x+&") end if res.space and tag:match("c") then spaceworks("{\\"..tag.."&H000000&fake} ","&H%x+&") end END=text:match("^.*\\"..tag.."&H%x+&(.-)$") val1=tagtab[1]:match("\\"..tag.."(&H%x+&)") val2=tagtab[count]:match("\\"..tag.."(&H%x+&)") B1,G1,R1=val1:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=val2:match("(%x%x)(%x%x)(%x%x)") A1=val1:match("&H(%x%x)&") A2=val2:match("&H(%x%x)&") for t=2,count-1 do if A1 then nA1=(tonumber(A1,16)) nA2=(tonumber(A2,16)) nAC=currentval(nA1,nA2,t) valc=tohex(round(nAC)) else nR1=(tonumber(R1,16)) nR2=(tonumber(R2,16)) nG1=(tonumber(G1,16)) nG2=(tonumber(G2,16)) nB1=(tonumber(B1,16)) nB2=(tonumber(B2,16)) nRC=currentval(nR1,nR2,t) nGC=currentval(nG1,nG2,t) nBC=currentval(nB1,nB2,t) RC=tohex(round(nRC)) GC=tohex(round(nGC)) BC=tohex(round(nBC)) valc=BC..GC..RC end tagtab[t]=tagtab[t]:gsub("(\\"..tag..")(&H%x+&)","%1&H"..valc.."&") end nt=END for a=count,1,-1 do nt=tagtab[a]..nt end nt=nt:gsub("{\\%d?%a+&H%x+&fake}","") text=nt:gsub("|t%b()",function(t) return t:gsub("|","\\") end) end end text=text:gsub("\\1c&","\\c&") :gsub("_s_"," ") line.text=text subs[i]=line end end function currentval(val1,val2,t) return (val2-val1)/(count-1)*(t-1)+val1 end function spaceworks(faketag,value) nocom=text:gsub("%b{}","") letrz=nocom:gsub(" ","") lcount=re.find(letrz,".") if count==#lcount and nocom:match(" ") then text=text:gsub("{[^\\}]-}",function(com) return com:gsub(" ","_s_") end) :gsub(" ",faketag) tagtab={} for tt in text:gmatch(".-\\"..tag..value) do table.insert(tagtab,tt) end count=#tagtab end end function mirror(subs,sel) taglist={"frz","frx","fry","fax","fay","posx","posy","orgx","orgy","mov1","mov2","mov3","mov4","clipx","clipy"} tagval={} line=subs[sel[1]] text=line.text tags=text:match(STAG) if not tags then t_error("No tags on line 1.",true) end tags=tags:gsub("\\t%b()","") for x=1,#taglist do tag=taglist[x] val=getval(tags,val) tagval[x]=val end for z=2,#sel do i=sel[z] progress("Processing line: "..z-1 .."/"..#sel-1) line=subs[i] text=line.text tags=text:match(STAG) or "" text=text:gsub(STAG,"") for x=1,#taglist do tag=taglist[x] if res[tag] then val1=tonumber(tagval[x]) val2=tonumber(getval(tags,val2)) if val1 and val2 then tags=replaceval(tags) end end end line.text=tags..text subs[i]=line end end function getval(tags,val) val=tags:match("\\"..tag.."([^\\}]+)") if tag=="posx" then val=tags:match("\\pos%(([%d%.%-]+)") end if tag=="posy" then val=tags:match("\\pos%([%d%.%-]+,([%d%.%-]+)") end if tag=="orgx" then val=tags:match("\\org%(([%d%.%-]+)") end if tag=="orgy" then val=tags:match("\\org%([%d%.%-]+,([%d%.%-]+)") end if tag=="mov1" then val=tags:match("\\move%(([%d%.%-]+)") end if tag=="mov2" then val=tags:match("\\move%([%d%.%-]+,([%d%.%-]+)") end if tag=="mov3" then val=tags:match("\\move%([%d%.%-]+,[%d%.%-]+,([%d%.%-]+)") end if tag=="mov4" then val=tags:match("\\move%([%d%.%-]+,[%d%.%-]+,[%d%.%-]+,([%d%.%-]+)") end if tag=="clipx" then val=tags:match("\\clip%(([%d%.%-]+)") end if tag=="clipy" then val=tags:match("\\clip%([%d%.%-]+,([%d%.%-]+)") end return val end function replaceval(tags) val3=val1+(val1-val2) tags=tags:gsub("\\"..tag..val2,"\\"..tag..val3) if tag=="posx" then tags=tags:gsub("(\\pos%()([%d%.%-]+)","%1"..val3) end if tag=="posy" then tags=tags:gsub("(\\pos%([%d%.%-]+,)([%d%.%-]+)","%1"..val3) end if tag=="orgx" then tags=tags:gsub("(\\org%()([%d%.%-]+)","%1"..val3) end if tag=="orgy" then tags=tags:gsub("(\\org%([%d%.%-]+,)([%d%.%-]+)","%1"..val3) end if tag=="mov1" then tags=tags:gsub("(\\move%()([%d%.%-]+)","%1"..val3) end if tag=="mov2" then tags=tags:gsub("(\\move%([%d%.%-]+,)([%d%.%-]+)","%1"..val3) end if tag=="mov3" then tags=tags:gsub("(\\move%([%d%.%-]+,[%d%.%-]+,)([%d%.%-]+)","%1"..val3) end if tag=="mov4" then tags=tags:gsub("(\\move%([%d%.%-]+,[%d%.%-]+,[%d%.%-]+,)([%d%.%-]+)","%1"..val3) end if tag=="clipx" then tags=tags:gsub("(\\clip%()([%d%.%-]+)(,[%d%.%-]+,)([%d%.%-]+)",function(a,b,c,d) return a..val3..c..d-(b- val3) end) end if tag=="clipy" then tags=tags:gsub("(\\clip%([%d%.%-]+,)([%d%.%-]+)(,[%d%.%-]+,)([%d%.%-]+)",function(a,b,c,d) return a..val3..c..d-(b-val3) end) end return tags end

-- reanimatools -- function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") val="[%d%-&]" if not tgt then tgt=tg:match("(\\%d?%a+)%b()") val="%b()" end if not tgt then tgt=tg:match("\\fn") val="" end if not tgt then t_error("adding tag '"..tg.."' failed.") end if tgt:match("clip") then txt,r=txt:gsub("^({[^}]-)\\i?clip%b()","%1"..tg) if r==0 then txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end elseif no_tf:match("^({[^}]-)"..tgt..val) then txt=txt:gsub("^({[^}]-)"..tgt..val.."[^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function styleget(subs) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i]) end if subs[i].class=="dialogue" then break end end end function stylechk(sn) for i=1,#styles do if sn==styles[i].name then sr=styles[i] if styles[i].name=="Default" then defaref=styles[i] end break end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function recalculator(subs,sel) ADD=aegisub.dialog.display ak=aegisub.cancel ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" GUI={ {x=2,y=0,width=3,class="floatedit",name="pc",value=100,min=0,hint="Multiply"}, {x=2,y=1,width=3,class="floatedit",name="add",value=0,hint="Add (use negative to subtract)"}, {x=2,y=2,width=3,class="floatedit",name="altval",value=0,hint="Multiply/Add based on button P"}, {x=2,y=3,width=3,class="intedit",name="rnd",value=default_rounding,min=0,max=4,hint="How many decimal places should be allowed"},

{x=0,y=0,width=2,class="label",label="Change values to:"}, {x=0,y=1,width=2,class="label",label="Increase values by:"}, {x=0,y=2,width=2,class="checkbox",name="alt",label="Alternative 2nd value",value=false,hint="Affects fscy, ybord, yshad, fry, fay, all Y coordinates, fad2, t2"}, {x=0,y=3,width=2,class="label",label="Rounding:"},

{x=5,y=0,width=3,class="label",label="% Recalculator v"..script_version},

{x=0,y=4,class="checkbox",name="fscx",label="fscx"}, {x=1,y=4,class="checkbox",name="fscy",label="fscy"}, {x=2,y=4,class="checkbox",name="fs",label="fs"}, {x=3,y=4,class="checkbox",name="fsp",label="fsp"}, {x=4,y=4,class="checkbox",name="blur",label="blur"}, {x=5,y=4,class="checkbox",name="be",label="be"},

{x=0,y=5,class="checkbox",name="bord",label="bord"}, {x=1,y=5,class="checkbox",name="shad",label="shad"}, {x=2,y=5,class="checkbox",name="xbord",label="xbord"}, {x=3,y=5,class="checkbox",name="ybord",label="ybord"}, {x=4,y=5,class="checkbox",name="xshad",label="xshad "}, {x=5,y=5,class="checkbox",name="yshad",label="yshad"},

{x=0,y=6,class="checkbox",name="frz",label="frz"}, {x=1,y=6,class="checkbox",name="frx",label="frx"}, {x=2,y=6,class="checkbox",name="fry",label="fry"}, {x=3,y=6,class="checkbox",name="fax",label="fax"}, {x=4,y=6,class="checkbox",name="fay",label="fay"}, {x=5,y=6,class="checkbox",name="kara",label="kara",hint="k/kf/ko. only the first one in the line."},

{x=0,y=7,class="checkbox",name="posx",label="pos x"}, {x=1,y=7,class="checkbox",name="posy",label="pos y"}, {x=2,y=7,class="checkbox",name="orgx",label="org x"}, {x=3,y=7,class="checkbox",name="orgy",label="org y"}, {x=4,y=7,class="checkbox",name="fad1",label="fad 1"}, {x=5,y=7,class="checkbox",name="fad2",label="fad 2"},

{x=4,y=8,width=2,class="checkbox",name="allpos",label="all pos/move/org ", hint="same as all 8 checkboxes. \naffects only existing tags."}, {x=6,y=8,width=2,class="label",label="------"},

{x=0,y=9,class="checkbox",name="clipx",label="clip x"}, {x=1,y=9,class="checkbox",name="clipy",label="clip y"}, {x=2,y=9,class="checkbox",name="drawx",label="draw x"}, {x=3,y=9,class="checkbox",name="drawy",label="draw y"}, {x=4,y=9,class="checkbox",name="ttim1",label="\\t 1",hint="\\t timecode 1"}, {x=5,y=9,class="checkbox",name="ttim2",label="\\t 2",hint="\\t timecode 2"}, {x=6,y=9,width=2,class="checkbox",name="anchor",label="anchor clip",hint="anchor clip with Multiply"},

{x=6,y=2,width=2,class="label",label="-v- Regradient -v-"}, {x=6,y=3,class="checkbox",name="alpha",label="alpha"}, {x=7,y=3,class="checkbox",name="space",label="[ ]",value=true,hint="workaround for spaces with full-line GBC"}, {x=0,y=10,width=3,class="checkbox",name="byline",label="multiply/add more with each line"},

{x=3,y=10,width=2,class="checkbox",name="regtag",label="regular tags",value=true}, {x=5,y=10,width=2,class="checkbox",name="tftag",label="tags in transforms",value=true}, {x=6,y=1,width=2,class="checkbox",name="rpt",label="repeat last"}, } for z=1,4 do table.insert(GUI,{x=z-1,y=8,class="checkbox",name="mov"..z,label="move"..z.." "}) table.insert(GUI,{x=6,y=z+3,class="checkbox",name=z.."a",label=z.."a"}) table.insert(GUI,{x=7,y=z+3,class="checkbox",name=z.."c",label=z.."c"}) end chk=","..checked.."," chk=chk:gsub(" *, *",",") :gsub("\t","\\t") for key,val in ipairs(GUI) do if val.class=="checkbox" and chk:match(","..val.label:gsub(" *$","")..",") then val.value=true end end repeat if P=="Clear" then for key,val in ipairs(GUI) do if val.class=="checkbox" and val.name~="anchor" then val.value=false end if val.name=="anchor" then val.value=res.anchor end if val.name=="alt" then val.value=res.alt end if val.name=="regtag" then val.value=res.regtag end if val.name=="tftag" then val.value=res.tftag end if val.name=="space" then val.value=res.space end if val.class:match("edit") then val.value=res[val.name] end end end P,res=ADD(GUI,{"Multiply","Add","Mirror","Regradient","Clear","Cancel"},{ok='Multiply',cancel='Cancel'}) until P~="Clear" if P=="Cancel" then ak() end if res.rpt and lastres then res=lastres end if res.allpos then res.posx=true res.posy=true res.orgx=true res.orgy=true res.mov1=true res.mov2=true res.mov3=true res.mov4=true end if P=="Multiply" or P=="Add" then styleget(subs) multiply(subs,sel) end if P=="Regradient" then regrad(subs,sel) end if P=="Mirror" then mirror(subs,sel) end lastres=res aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(recalculator) else aegisub.register_macro(script_name,script_description,recalculator) end --[[ alternative to aegisub's select tool. unlike that one, this can also select by layer. version 2.0 includes sorting of selected/all lines, by the same markers as the selecting uses.

D 'Select/sort' This is what the search string is compared against. There are 4 'numbers' items and 4 'text' items. E 'Used area' 'current selection' - only lines in the current selection will be scanned. S 'Numbers.' For 'numbers' items, you can select lines with higher or lower layer/duration instead of just exact match. C With "==", you can specify a range, like 2-4, to select for example lines with layers 2-4. R 'Match this' Only numbers for 'numbers' items. Duration is in milliseconds. I 'case sensitive' Obviously applies only to 'text' items. P 'exact match' Same. T 'use regexp' Not sure how well this is working, but it should work. Only for 'text' items. I 'mod' Modifies some functions: O -> "sort by time" - sorts by end time N -> "OP/ED in style" - includes any lines timed between the first and last lines of OP/ED (for including signs in OP/ED) -> "move sel. to top/bottom" - selection doesn't follow the moved lines Presets: same text (contin.) - reads texts of selected lines and selects all following lines with the same texts until it reaches new text same text (all lines) - selects all lines in the script with the same texts as the current selection (clean text - no tags/comments) move sel. up/down - moves the selection by 1 unless given a different number in the match field range of lines - set a range of lines to be selected, like "1530-2460" --]]

-- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#selectrix script_name="Selectricks" script_description="Selectricks and Sortricks" script_author="unanimated" script_version="2.82" script_namespace="ua.Selectrix" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.8.2" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end

-- SETTINGS -- you can choose from the options below to change the default settings search_sort="text" -- "layer","style","actor","effect","text" select_from="current selection" -- "current selection","all lines" matches_or_not="matches" -- "matches","doesn't match" numbers_option="==" -- "==",">=","<=" case_sensitive=false -- true/false exact_match=false -- true/false use_regexp=false -- true/false exclude_commented=true -- true/false load_in_editor=false -- true/false remember_last_search=true -- true/false [will remember last search string] remember_select_sort=true -- true/false [will remember last select/sort mode] remember_case=false -- true/false [will remember case sensitive option] remember_regexp=false -- true/false [will remember regexp option] remember_exact=false -- true/false [will remember exact match option] remember_preset=true -- true/false [will remember last selected preset] your_retarded=false -- set to true if your skiddiks

-- end of settings ------re=require'aegisub.re' unicode=require'aegisub.unicode' clipboard=require("aegisub.clipboard") ulower=unicode.to_lower_case

-- Analyze Line function analyze(l) text=l.text style=l.style dur=l.end_time-l.start_time dura=dur/1000 txt=text:gsub("{[^}]-}","") :gsub("\\N","") visible=text:gsub("{\\alpha&HFF&}[^{}]-{[^{}]-}","") :gsub("{\\alpha&HFF&}[^{}]*$","") :gsub("{[^{}]- }","") :gsub("\\[Nn]","*") :gsub("%s?%*+%s?"," ") :gsub("^%s+","") :gsub("%s+$","") wrd=0 for word in txt:gmatch("([%a\']+)") do wrd=wrd+1 end chars=visible:gsub(" ","") :gsub("[%.,%?!'\"—]","") char=chars:len() cps=math.ceil(char/dura) if dur==0 then cps=0 end blur=text:match("\\blur([%d%.]+)") blur=tonumber(blur) if blur==nil then blur=0 end comment="" for com in text:gmatch("{[^\\}]-}") do comment=comment..com end end

-- Check Style function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st end if subs[i].name=="Default" then dstyleref=subs[i] end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end

-- SELECT function slct(subs, sel) sel2={} eq=res.equal for i=#sel,1,-1 do local line=subs[sel[i]] analyze(line) a=sel[i]

nums=0 if res.mode=="style" then search_area=style end if res.mode=="actor" then search_area=line.actor end if res.mode=="effect" then search_area=line.effect end if res.mode=="text" then search_area=text if res.mod then search_area=comment end end if res.mode=="visible text (no tags)" then search_area=text:gsub("{[^}]-}","") end if res.mode=="layer" then numb=line.layer nums=1 end if res.mode=="duration" then numb=dur nums=1 end if res.mode=="word count" then numb=wrd nums=1 end if res.mode=="character count" then numb=char nums=1 end if res.mode=="char. per second" then numb=cps nums=1 end if res.mode=="blur" then numb=blur nums=1 end if res.mode=="left margin" then numb=line.margin_l nums=1 end if res.mode=="right margin" then numb=line.margin_r nums=1 end if res.mode=="vertical margin" then numb=line.margin_t nums=1 end

if nums==1 then numbers=true else numbers=false end

nonregexp=esc(res.match) nonregexplower=ulower(nonregexp) regexplower=ulower(res.match)

if not numbers then s_area_lower=ulower(search_area) end

if numbers then num1,num2=res.match:match("([%d%.]+)%-([%d%.]+)") if num2~=nil then nmbrs={} for n=num1,num2 do table.insert(nmbrs,n) end else nmbrs={res.match} end if eq=="==" then numatch=0 for n=1,#nmbrs do if numb==tonumber(nmbrs[n]) then numatch=1 end end if numatch==0 then table.remove(sel,i) end end if eq==">=" and numbtonumber(res.match) then table.remove(sel,i) end end if not numbers then if res.case then if res.exact then if search_area~=res.match then table.remove(sel,i) end else if res.regexp then matches=re.find(search_area,res.match) if matches==nil then table.remove(sel,i) end else if not search_area:match(nonregexp) then table.remove(sel,i) end end end end

if not res.case then if res.exact then if s_area_lower~=ulower(res.match) then table.remove(sel,i) end else if res.regexp then matches=re.find(search_area,res.match,re.ICASE) if matches==nil then table.remove(sel,i) end else if not s_area_lower:match(nonregexplower) then table.remove(sel,i) end end end end end

if res.nocom and line.comment and sel[i]==a then table.remove(sel,i) end

if sel[i]~=a then if res.nocom and line.comment then else table.insert(sel2,a) end end end

if res.nomatch=="doesn't match" then return sel2 else return sel end end

-- PRESET All function preset(subs, sel) act=sel[1] if res.pres:match("same text") then marks={} lm=nil for x,i in ipairs(sel) do rine=subs[i] mark=rine.text:gsub("{[^}]-}","") if mark=="" then mark="_empty_" end if mark~=lm then table.insert(marks,mark) end lm=mark end end if res.pres=="range of lines" then range_st,range_end=res.match:match("(%d+)%-(%d+)") if range_st==nil then range_st=res.match:match("%d+") range_end=range_st end if range_st==nil then aegisub.dialog.display({{class="label",label="Error: No numbers given."}},{"OK"},{close='OK'}) if cancel then aegisub.cancel() end end end for i=#sel,1,-1 do table.remove(sel,i) end opst=10000000 opet=0 edst=10000000 edet=0 for i=1,#subs do if subs[i].class=="dialogue" then local line=subs[i] local text=line.text local st=line.style local start=line.start_time local endt=line.end_time local nc=text:gsub("{[^\\}]-}","") if res.pres=="Default style - All" then if st:match("Defa") or st:match("Alt") then table.insert(sel,i) end end if res.pres=="nonDefault - All" then if not st:match("Defa") and not st:match("Alt") then table.insert(sel,i) end end if res.pres=="OP in style" then if st:match("OP") then table.insert(sel,i) if startopet then opet=endt end end end if res.pres=="ED in style" then if st:match("ED") then table.insert(sel,i) if startedet then edet=endt end end end if res.pres=="layer 0" then if line.layer==0 then table.insert(sel,i) end end if res.pres=="lines w/ comments 1" then if not res.nocom or not line.comment then if text:match("{[^\\}]-}") then table.insert(sel,i) end end end if res.pres=="same text (contin.)" then if i==act then table.insert(sel,i) end if i>act then ct=text:gsub("{[^}]-}","") ch=0 for m=1,#marks do if marks[m]==ct then ch=1 end end if ch==1 then table.insert(sel,i) else break end end end if res.pres=="same text (all lines)" then ct=text:gsub("{[^}]-}","") ch=0 for m=1,#marks do if marks[m]==ct then ch=1 end end if ch==1 then table.insert(sel,i) end end if res.pres=="skiddiks, your their?" then if st:match("Defa") or st:match("Alt") then if nc:match("[Yy]ou\'?re?%s") or nc:match("[Tt]hey?\'?re") or nc:match("[Tt]heir") then table.insert(sel,i) if your_retarded then line.effect=line.effect.." your retarded" subs[i]=line end end end end if res.pres=="its/id/ill/were/wont" then if st:match("Defa") or st:match("Alt") then nc=" "..nc:lower().." " if nc:match(" its ") or nc:match(" id ") or nc:match(" ill ") or nc:match(" were ") or nc:match(" wont ") then table.insert(sel,i) end end end if res.pres=="range of lines" then if startline==nil then startline=i end ind=i-startline+1 if ind>=tonumber(range_st) and ind<=tonumber(range_end) then table.insert(sel,i) end end end end -- OP/ED mod if res.mod and res.pres:match("in style") then for i=1,#subs do if subs[i].class=="dialogue" then local line=subs[i] local text=line.text local st=line.style local start=line.start_time local endt=line.end_time if res.pres=="OP in style" then if not st:match("OP") and start>opst and endtedst and endt

-- PRESET From Selection function presel(subs, sel) sorttab={} if res.pres=="move sel. up" then table.sort(sel,function(a,b) return a>b end) end if res.match:match("^%d+$") then moveby=res.match:match("^(%d+)$") else moveby=1 end edtr=0 for i=#sel,1,-1 do local line=subs[sel[i]] local text=line.text local st=line.style if res.pres=="no-blur signs" then edtr=1 blur=text:match("\\blur([%d%.]+)") if st:match("Defa") or st:match("Alt") or st:match("OP") or st:match("ED") or blur~=nil then table.remove(sel,i) end end if res.pres=="commented lines" and not line.comment then edtr=1 table.remove(sel,i) end if res.pres=="lines w/ comments 2" then edtr=1 if res.nocom and line.comment or not text:match("{[^\\}]-}") then table.remove(sel,i) end end if res.pres=="move sel. to the top" or res.pres=="move sel. to bottom" then line.ind=i table.insert(sorttab,subs[sel[i]]) subs.delete(sel[i]) end if res.pres=="move sel. up" then sel[i]=sel[i]-moveby end if res.pres=="move sel. down" then sel[i]=sel[i]+moveby end end if res.pres=="move sel. to the top" then cs=1 repeat if subs[cs].class~="dialogue" then cs=cs+1 end until subs[cs].class=="dialogue" for s=1,#sorttab do subs.insert(cs,sorttab[s]) end if not res.mod then sel={} for sl=cs,cs+#sorttab-1 do table.insert(sel,sl) end end end if res.pres=="move sel. to bottom" then for s=#sorttab,1,-1 do subs.append(sorttab[s]) end if not res.mod then sel={} for sl=#subs,#subs-#sorttab+1,-1 do table.insert(sel,sl) end end end if res.pres=="sel: last to top" then sell={} for i=1,#sel do l=subs[sel[i]] table.insert(sell,l) end for i=1,#sel do l=subs[sel[i]] if i==1 then subs[sel[i]]=sell[#sel] else subs[sel[i]]=sell[i-1] end end end if res.pres=="sel: first to bottom" then sell={} for i=1,#sel do l=subs[sel[i]] table.insert(sell,l) end for i=1,#sel do l=subs[sel[i]] if i==#sel then subs[sel[i]]=sell[1] else subs[sel[i]]=sell[i+1] end end end

return sel end

-- SELECTRIX GUI -- function konfig(subs,sel) edtr=0 main_mode= {"------text------","style","actor","effect","text","visible text (no tags)","------numbers------","layer","duration","word count","character count","char. per second","blur","left margin","right margin","vertical margin","------sorting only------","sort by time","reverse","width of text","dialogue first","dialogue last","ts/dialogue/oped","{TS} to the top","masks to the bottom","by comments"} presetses={"Default style - All","nonDefault - All","OP in style","ED in style","layer 0","lines w/ comments 1","same text (contin.)","same text (all lines)","skiddiks, your their?","its/id/ill/were/wont","range of lines","----from selection----","no-blur signs","commented lines","lines w/ comments 2","move sel. up","move sel. down","------sorting------","move sel. to the top","move sel. to bottom","sel: first to bottom","sel: last to top"} GUI= { {x=0,y=0,class="label",label="Select/sort:"}, {x=0,y=1,class="label",label="Used area:"}, {x=0,y=2,class="label",label="Numbers:"}, -- MAIN MODE {x=1,y=4,width=3,class="edit",name="match",value=lastmatch or ""}, {x=1,y=0,class="dropdown",name="mode",value=lastmode or search_sort,items=main_mode}, {x=1,y=1,class="dropdown",name="selection",value=select_from,items={"current selection","all lines"}}, {x=1,y=2,class="dropdown",name="equal",value=numbers_option,items={"==",">=","<="},hint="options for layer/duration"}, {x=1,y=3,class="dropdown",name="nomatch",value=matches_or_not,items={"matches","doesn't match"}}, {x=0,y=4,class="label",label="Match this:"},

-- PRESETS {x=0,y=5,class="label",label="Sel. preset:"}, {x=1,y=5,class="dropdown",name="pres",value=lastpreset or "Default style - All",items=presetses}, {x=2,y=0,class="label",label="Text: "}, {x=3,y=0,class="checkbox",name="case",label="case sensitive",value=lastcase or case_sensitive}, {x=3,y=1,class="checkbox",name="exact",label="exact match",value=lastexact or exact_match}, {x=2,y=1,class="checkbox",name="regexp",label="regexp",value=lastregexp or use_regexp}, {x=2,y=2,width=2,class="checkbox",name="nocom",label="exclude commented lines",value=exclude_commented}, {x=2,y=3,class="label",label="Sorting:"}, {x=3,y=3,class="checkbox",name="rev",label="reversed",value=false}, {x=2,y=5,class="checkbox",name="mod",label="mod",value=false}, {x=3,y=5,class="checkbox",name="editor",label="load in editor",value=load_in_editor},

} buttons={"Set Selection","Preset","Sort","Cancel"} pressed, res=aegisub.dialog.display(GUI,buttons,{ok='Set Selection',close='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Preset" then if res.pres=="no-blur signs" or res.pres=="commented lines" or res.pres=="lines w/ comments 2" or res.pres:match"move sel." or res.pres:match"sel:" then sel=presel(subs, sel) else edtr=1 preset(subs, sel) end end if pressed=="Sort" and res.selection=="current selection" then sorting(subs, sel) end if pressed=="Sort" and res.selection=="all lines" then sel=selectall(subs, sel) sorting(subs, sel) end

if pressed=="Set Selection" and res.selection=="current selection" then edtr=1 slct(subs, sel) end if pressed=="Set Selection" and res.selection=="all lines" then edtr=1 sel=selectall(subs, sel) slct(subs, sel) end if remember_last_search then lastmatch=res.match end if remember_select_sort then lastmode=res.mode end if remember_case then lastcase=res.case end if remember_regexp then lastregexp=res.regexp end if remember_exact then lastexact=res.exact end if remember_preset then lastpreset=res.pres end return sel end

-- SORTING function sorting(subs,sel) subtable={} -- lines into table for x, i in ipairs(sel) do local l=subs[i] analyze(l) l.i=x l.wrd=wrd l.ch=char l.cps=cps l.ml=l.margin_l l.mr=l.margin_r l.mv=l.margin_t nocomment=l.text:gsub("{[^}]-}","") :gsub("%s?\\N%s?"," ") if style:match("Defa") or style:match("Alt") then l.st=1 else l.st=2 end l.sdo=1 if style:match("Defa") or style:match("Alt") then l.sdo=2 end if style:match("OP") or style:match("ED") then l.sdo=3 end if l.text:match("{TS") then l.ts=1 else l.ts=2 end if l.text:match("{[^\\}]-}") then l.com=l.text:match("{[^\\}]-}") else l.com="" end blur=text:match("\\blur([%d%.]+)") blur=tonumber(blur) if blur==nil then blur=0 end l.bl=blur if text:match("\\p1") then l.mask=1 else l.mask=0 end if res.mode=="width of text" then if l.style=="Default" and dstyleref~=nil then styleref=dstyleref else styleref=stylechk(subs,l.style) end l.width=aegisub.text_extents(styleref,nocomment) end table.insert(subtable,l) end

-- sort lines if res.mode=="layer" then table.sort(subtable,function(a,b) return a.layerb.i end) end if res.mode=="width of text" then table.sort(subtable,function(a,b) return a.widthb.st or (a.st==b.st and a.i

-- lines back for x, i in ipairs(sel) do local l=subtable[x] local r=subtable[#subtable-x+1] if res.rev then subs[i]=r else subs[i]=l end end end

-- Select All function selectall(subs, sel) sel={} for i=1, #subs do if subs[i].class=="dialogue" then table.insert(sel,i) end end return sel end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function t_error(message,cancel) aegisub.dialog.display({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then aegisub.cancel() end end

-- EDITOR function editlines(subs, sel) editext="" dura="" for x, i in ipairs(sel) do if aegisub.progress.is_cancelled() then aegisub.cancel() end aegisub.progress.title(string.format("Reading line: %d/%d",x,#sel)) line=subs[i] text=line.text dur=line.end_time-line.start_time dur=dur/1000 char=text:gsub("{[^}]-}","") :gsub("\\[Nn]","*") :gsub("%s?%*+%s?"," ") :gsub(" ","") :gsub("[%.,%? !'\"—]","") linelen=char:len() cps=math.ceil(linelen/dur) if tostring(dur):match("%.%d$") then dur=dur.."0" end if not tostring(dur):match("%.") then dur=dur..".00" end if cps<10 then cps=" "..cps end if dur=="0.00" then cps="n/a" end

if x~=#sel then editext=editext..text.."\n" dura=dura..dur.." .. "..cps.." .. "..linelen.."\n" end if x==#sel then editext=editext..text dura=dura..dur.." .. "..cps.." .. "..linelen end end editbox(subs, sel) if failt==1 then editext=res.dat editbox(subs, sel) end return sel end

-- EDITOR GUI function editbox(subs, sel) aegisub.progress.title("Loading Editor...") if #sel<=4 then boxheight=7 end if #sel>=5 and #sel<9 then boxheight=8 end if #sel>=9 and #sel<15 then boxheight=math.ceil(#sel*0.8) end if #sel>=15 and #sel<18 then boxheight=12 end if #sel>=18 then boxheight=15 end if editext:len()>1500 and boxheight==7 then boxheight=8 end if editext:len()>1800 and boxheight==8 then boxheight=9 end nocom=editext:gsub("{[^}]-}","") words=0 plaintxt=nocom:gsub("%p","") for wrd in plaintxt:gmatch("%S+") do words=words+1 end if lastrep1==nil then lastrep1="" end if lastrep2==nil then lastrep2="" end GUI= { {x=0,y=0,width=52,height=1,class="label",label="Text"}, {x=52,y=0,width=5,height=1,class="label",label="Duration | CPS | chrctrs"},

{x=0,y=boxheight+1,class="label",label="Replace:"}, {x=1,y=boxheight+1,width=15,height=1,class="edit",name="rep1",value=lastrep1}, {x=16,y=boxheight+1,class="label",label="with"}, {x=17,y=boxheight+1,width=15,height=1,class="edit",name="rep2",value=lastrep2},

{x=0,y=1,width=52,height=boxheight,class="textbox",name="dat",value=editext}, {x=52,y=1,width=5,height=boxheight,class="textbox",name="durr",value=dura,hint="This is informative only. \nCPS=Characters Per Second"},

{x=32,y=boxheight+1,width=20,height=1,class="edit",name="info",value="Lines loaded: "..#sel..", Characters: "..editext:len()..", Words: "..words }, {x=52,y=boxheight+1,width=5,height=1,class="label",label="Multi-Line Editor v1.33"}, } buttons={"Save","Replace","Remove tags","Rm. comments","Remove \"- \"","Remove \\N","Add italics","Add \\an8","Reload text","Taller GUI","Cancel"} repeat if pressed=="Replace" or pressed=="Add italics" or pressed=="Add \\an8" or pressed=="Remove \\N" or pressed=="Reload text" or pressed=="Remove tags" or pressed=="Rm. comments" or pressed=="Remove \"- \"" or pressed=="Taller GUI" then

if pressed=="Add italics" then res.dat=res.dat :gsub("$","\n") :gsub("(.-)\n","{\\i1}%1\n") :gsub("{\\i1}{\\","{\\i1\\") :gsub("\n$","") end if pressed=="Add \\an8" then res.dat=res.dat :gsub("$","\n") :gsub("(.-)\n","{\\an8}%1\n") :gsub("{\\an8}{\\","{\\an8\\") :gsub("\n$","") end if pressed=="Remove \\N" then res.dat=res.dat :gsub("%s?\\N%s?"," ") end if pressed=="Remove tags" then res.dat=res.dat:gsub("{%*?\\[^}]-}","") end if pressed=="Rm. comments" then res.dat=res.dat:gsub("{[^\\}]-}","") :gsub("{[^\\}]-\\N[^\\}]-}","") end if pressed=="Remove \"- \"" then res.dat=res.dat:gsub("%- ","") end if pressed=="Replace" then rep1=esc(res.rep1) res.dat=res.dat:gsub(rep1,res.rep2) end if pressed=="Taller GUI" then boxheight=boxheight+1 for key,val in ipairs(GUI) do if val.y==1 then val.height=val.height+1 end if val.y>1 then val.y=val.y+1 end end end

for key,val in ipairs(GUI) do if pressed~="Reload text" then if val.name=="dat" then val.value=res.dat end if val.name=="durr" then val.value=res.durr end if val.name=="info" then val.value=res.info end if val.name=="oneline" then val.value=res.oneline end if val.name=="rep1" then val.value=res.rep1 end if val.name=="rep2" then val.value=res.rep2 end else if val.name=="dat" then val.value=editext end end end end pressed, res=aegisub.dialog.display(GUI,buttons,{save='Save',close='Cancel'}) until pressed=="Save" or pressed=="Cancel"

if pressed=="Cancel" then aegisub.cancel() end if pressed=="Save" then savelines(subs, sel) end lastrep1=res.rep1 lastrep2=res.rep2 return sel end

-- EDITOR SAVE function savelines(subs, sel) aegisub.progress.title("Saving...") local data={} raw=res.dat.."\n" if #sel==1 then raw=raw:gsub("\n(.)","\\N%1") raw=raw:gsub("\\N "," \\N") end for dataline in raw:gmatch("(.-)\n") do table.insert(data,dataline) end failt=0 if #sel~=#data and #sel>1 then failt=1 else for x, i in ipairs(sel) do line=subs[i] line.text=data[x] subs[i]=line end end if failt==1 then aegisub.dialog.display({{class="label", label="Line count of edited text does not \nmatch the number of selected lines.",x=0,y=0,width=1,height=2}},{"OK"}) clipboard.set(res.dat) end aegisub.set_undo_point(script_name) return sel end function selector(subs,sel,act) sel=konfig(subs,sel,act) if edtr==1 and res.editor then editlines(subs,sel,act) end aegisub.set_undo_point(script_name) if res.nomatch=="doesn't match" and pressed=="Set Selection" then return sel2, act else return sel, act end end if haveDepCtrl then depRec:registerMacro(selector) else aegisub.register_macro(script_name,script_description,selector) end -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#multiedit script_name="Multi-Line Editor" script_description="Multi-Line Editor" script_author="unanimated" script_version="1.7" script_namespace="ua.MultiLineEditor" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="1.7.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' unicode=require'aegisub.unicode' clipboard=require("aegisub.clipboard") function editlines(subs,sel) ADD=aegisub.dialog.display ak=aegisub.cancel editext="" dura="" edeff="" edact="" edst="" for z,i in ipairs(sel) do progress("Reading line: "..z.."/"..#sel.." ("..math.floor(z/#sel*100).."%)") line=subs[i] text=line.text dur=(line.end_time-line.start_time)/1000 char=text:gsub("%b{}",""):gsub("\\[Nnh]","*"):gsub("%s?%*+%s?"," "):gsub("[%s%p]","") linelen=char:len() cps=math.ceil(linelen/dur) if tostring(dur):match("%.%d$") then dur=dur.."0" end if not tostring(dur):match("%.") then dur=dur..".00" end if cps<10 then cps=" "..cps end if dur=="0.00" then cps="n/a" end dura=dura..dur.." .. "..cps.." .. "..linelen.."\n" editext=editext..text.."\n" edst=edst..line.style.."\n" edact=edact..line.actor.."\n" edeff=edeff..line.effect.."\n" end editext=nRem(editext) dura=nRem(dura) edst=nRem(edst) edact=nRem(edact) edeff=nRem(edeff) editbox(subs,sel) if failt==1 then editext=res.dat editbox(subs,sel) end return sel end function editbox(subs,sel) progress("Loading Editor...") BH=math.ceil(#sel*0.66)+2 if BH<7 then BH=7 end repeat if editext:len()>=BH*200 then BH=BH+1 end until editext:len()=20 if BH>20 then BH=20 end b=BH+1 nocom=editext:gsub("%b{}","") :gsub("%—"," ") words=0 for wrd in nocom:gmatch("%S+") do words=words+1 end

R1={x=0,y=b,width=1,class="label",label="Replace:"} R2={x=1,y=b,width=15,class="edit",name="rep1",value=lastrep1 or ""} R3={x=16,y=b,width=1,class="label",label="with"} R4={x=17,y=b,width=15,class="edit",name="rep2",value=lastrep2 or ""} R5={x=32,y=b,width=12,class="edit",name="repl",value=""} R6={x=44,y=b,class="label",label=" "} R7={x=45,y=b,width=7,class="checkbox",name="whole",label="whole word only",hint="only without regexp"} R8={x=52,y=b,width=3,class="checkbox",name="reg",label="regexp ",value=regr} R9={x=55,y=b,width=2,class="checkbox",name="lua",label="lua",value=luar}

GUI1={R1,R2,R3,R4,R5,R6,R7,R8,R9, {x=0,y=0,width=10,class="label",label=" Multi-line Editor v"..script_version}, {x=52,y=0,width=5,class="label",label="Duration | CPS | chrctrs "}, {x=10,y=0,width=22,name="info",class="edit",value="Lines loaded: "..#sel..", Words: "..words..", Characters: "..editext:len()}, {x=32,y=0,width=3,class="checkbox",name="an",label="\\an8 "}, {x=35,y=0,width=3,class="checkbox",name="i",label="\\i1 "}, {x=38,y=0,width=3,class="checkbox",name="b",label="\\b1 "}, {x=41,y=0,width=5,class="checkbox",name="q",label="\\q2 "}, {x=47,y=0,width=4,class="checkbox",name="sent",label="Sentences",hint="Capitalise sentences\n(including start of lines)"}, {x=0,y=1,width=52,height=BH,class="textbox",name="dat",value=editext}, {x=52,y=1,width=5,height=BH,class="textbox",name="durr",value=dura,hint="This is informative only. \nCPS=Characters Per Second"}, }

GUI2={R1,R2,R3,R4,R5,R6,R7,R8,R9, {x=0,y=0,width=9,class="checkbox",name="rs",label="Style"}, {x=9,y=0,width=11,class="checkbox",name="ra",label="Actor"}, {x=20,y=0,width=12,class="checkbox",name="re",label="Effect"}, {x=32,y=0,width=12,class="checkbox",name="rt",label="Text",value=true}, {x=44,y=0,width=14,class="label",label="Checkboxes mark what Replacer applies to"}, {x=0,y=1,width=9,height=BH,class="textbox",name="dast",value=edst}, {x=9,y=1,width=11,height=BH,class="textbox",name="dact",value=edact}, {x=20,y=1,width=12,height=BH,class="textbox",name="deaf",value=edeff}, {x=32,y=1,width=28,height=BH,class="textbox",name="dat",value=editext}, } buttons={"Save","Replace","Remove tags","Rm. comments","Remove \"- \"","Remove \\N","Add tags","Capitalise","Switch","Taller GUI","Cancel"} GUI=GUI1 repeat if P~="Save" and P ~="Cancel" and P~=nil then if P=="Add tags" then tg="" if res.q then tg=tg.."{\\q2}" end if res.an then tg=tg.."{\\an8}" end if res.i then tg=tg.."{\\i1}" end if res.b then tg=tg.."{\\b1}" end tg=tg:gsub("}{","") res.dat=res.dat:gsub("$","\n"):gsub("(.-)\n",tg.."%1\n"):gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}"):gsub("\n$","") end if P=="Capitalise" then captest=res.dat:gsub("%b{}",""):gsub("\\N","") res.dat=res.dat:gsub("$","\n") if res.sent then res.dat=res.dat:gsub("(.-)\n",function(a) return sentences(a).."\n" end) elseif not captest:match("%l") then res.dat=res.dat:gsub("(.-)\n",function(a) a=lowercase(a) return capitalise(a).."\n" end) elseif not captest:match("%u") then res.dat=res.dat:gsub("(.-)\n",function(a) return uppercase(a).."\n" end) else res.dat=res.dat:gsub("(.-)\n",function(a) return lowercase(a).."\n" end) end res.dat=res.dat:gsub("\n$","") end if P=="Remove \\N" then res.dat=res.dat:gsub("%s*\\N%s*"," ") end if P=="Remove tags" then res.dat=res.dat:gsub("{%*?\\[^}]-}","") end if P=="Rm. comments" then res.dat=res.dat:gsub("{[^\\}]-}","") :gsub("{[^\\}]-\\N[^\\}]-}","") end if P=="Remove \"- \"" then res.dat=res.dat:gsub("^%- ","") :gsub("\n%- ","\n") :gsub("^({\\i1})%- ","%1") :gsub("\n({\\i1})%- ","\n%1") end if P=="Replace" then c=0 if res.rt or GUI==GUI1 then res.dat=replace(res.dat) end if GUI==GUI2 then if res.rs then res.dast=replace(res.dast) end if res.ra then res.dact=replace(res.dact) end if res.re then res.deaf=replace(res.deaf) end end res.repl=c.." replacements" end if P=="Taller GUI" then for key,val in ipairs(GUI1) do if val.y==1 then val.height=val.height+2 end if val.y>1 then val.y=val.y+2 end end for key,val in ipairs(GUI2) do if val.y==1 then val.height=val.height+2 end end end for key,val in ipairs(GUI) do val.value=res[val.name] end if P=="Switch" then if GUI==GUI1 then GUI=GUI2 info=res.info else GUI=GUI1 end for key,val in ipairs(GUI) do if val.name=="dat" then val.value=res[val.name] end end end for key,val in ipairs(GUI) do if val.name=="info" then val.value=info or res[val.name] end if val.name=="sent" then val.value=false end end end P,res=ADD(GUI,buttons,{save='Save',close='Cancel'}) until P=="Save" or P=="Cancel"

if P=="Cancel" then ak() end if P=="Save" then savelines(subs,sel) end lastrep1=res.rep1 lastrep2=res.rep2 regr=res.reg luar=res.lua return sel end function replace(d) if res.lua then d,r=d:gsub(res.rep1,res.rep2) elseif res.reg then r=re.find(d,res.rep1) or {} r=#r d=re.sub(d,res.rep1,res.rep2) else rep1=esc(res.rep1) rerep1=resc(res.rep1) if res.whole then r=re.find(d,"\\b"..rerep1.."\\b") or {} r=#r d=re.sub(d,"\\b"..rerep1.."\\b",res.rep2) else d,r=d:gsub(rep1,res.rep2) end end c=c+r return d end function lowercase(t) t=t:gsub("\\[Nnh]","{%1}") t=t:gsub("^([^{]*)",function(l) return ulower(l) end) t=t:gsub("}([^{]*)",function(l) return "}"..ulower(l) end) t=t:gsub("{(\\[Nnh])}","%1") return t end function uppercase(t) t=t:gsub("\\[Nnh]","{%1}") t=t:gsub("^([^{]*)",function(u) return uupper(u) end) t=t:gsub("}([^{]*)",function(u) return "}"..uupper(u) end) t=t:gsub("{(\\[Nnh])}","%1") return t end function sentences(t) somewords={"English","Japanese","American","British","German","French","Spanish","Monday","Tuesday","Wednes day","Thursday","Friday","Saturday","Sunday","January","February","April","June","July","August","September","Oct ober","November","December"} hnrfx={"%-san","%-kun","%-chan","%-sama","%-dono","%-se[nm]pai","%-on%a+an"} t=re.sub(t,[[^(["']?\l)]],function(l) return uupper(l) end) t=re.sub(t,[[^\{[^}]*\}(["']?\l)]],function(l) return uupper(l) end) t=re.sub(t,[[[\.\?!](\s|\s\\N|\\N)["']?(\l)]],function(l) return uupper(l) end) t=t:gsub(" i([' %?!%.,])"," I%1") t=t:gsub("\\Ni([' ])","\\NI%1") t=t:gsub(" m(arch %d)"," M%1") t=t:gsub(" a(pril %d)"," A%1") for l=1,#somewords do t=t:gsub(somewords[l]:lower(),somewords[l]) end for h=1,#hnrfx do t=t:gsub("([ %p]%l)(%l*"..hnrfx[h]..")",function(h,f) return h:upper()..f end) t=t:gsub("(\\N%l)(%l*"..hnrfx[h]..")",function(h,f) return h:upper()..f end) end t=re.sub(t,"\\b(of|in|from|\\d+st|\\d+nd|\\d+rd|\\d+th) m(arch|ay)\\b","\\1 M\\2") t=re.sub(t,"\\bm(r|rs|s)\\.","M\\1.") t=re.sub(t,"\\bdr\\.","Dr.") return t end function capitalise(txt) word={"A","About","Above","Across","After","Against","Along","Among","Amongst","An","And","Around","As","A t","Before","Behind","Below","Beneath","Beside","Between","Beyond","But","By","Despite","During","Except","For", "From","In","Inside","Into","Near","Nor","Of","On","Onto","Or","Over","Per","Sans","Since","Than","The","Through" ,"Throughout","Till","To","Toward","Towards","Under","Underneath","Unlike","Until","Unto","Upon","Versus","Via" ,"With","Within","Without","According to","Ahead of","Apart from","Aside from","Because of","Inside of","Instead of","Next to","Owing to","Prior to","Rather than","Regardless of","Such as","Thanks to","Up to","and Yet"} onore={"%-San","%-Kun","%-Chan","%-Sama","%-Dono","%-Se[nm]pai","%-On%a+an"} nokom={"^( ?)([^{]*)","(})([^{]*)"} for n=1,2 do txt=txt:gsub(nokom[n],function(no_t,t) t=t:gsub("\\[Nnh]","{%1}") t=re.sub(t,[[\b\l]],function(l) return uupper(l) end) t=re.sub(t,[[[I\l]'(\u)]],function(l) return ulower(l) end)

for r=1,#word do w=word[r] t=t:gsub("^ "..w.." "," "..w:lower().." ") t=t:gsub("([^%.:%?!]) "..w.." ","%1 "..w:lower().." ") t=t:gsub("([^%.:%?!]) (%b{})"..w.." ","%1 %2"..w:lower().." ") t=t:gsub("([^%.:%?!]) (%*Large_break%* ?)"..w.." ","%1 %2"..w:lower().." ") end

t=t:gsub("$","#") -- Roman numbers (this may mismatch some legit words - sometimes there just are 2 options and it's a guess) t=t:gsub("(%s?)([IVXLCDM])([ivxlcdm]+)([%s%p#])",function(s,r,m,e) return s..r..m:upper()..e end) t=t:gsub("([DLM])ID","%1id") t=t:gsub("DIM","Dim") t=t:gsub("MIX","Mix") t=t:gsub("Ok([%s%p#])","OK%1") for h=1,#onore do t=t:gsub(onore[h].."([%s%p#])",onore[h]:lower().."%1") end t=t:gsub("#$","") t=t:gsub("{(\\[Nnh])}","%1") return no_t..t end) end return txt end function savelines(subs,sel) progress("Saving...") tdat={} sdat={} adat={} edat={} tt=res.dat.."\n" if #sel==1 then tt=tt:gsub("\n(.)","\\N%1") tt=tt:gsub("\\N "," \\N") end ss=res.dast or "" if ss~="" then ss=ss.."\n" end aa=res.dact or "" if aa~="" then aa=aa.."\n" end ee=res.deaf or "" if ee~="" then ee=ee.."\n" end for dataline in tt:gmatch("(.-)\n") do table.insert(tdat,dataline) end for dataline in ss:gmatch("(.-)\n") do table.insert(sdat,dataline) end for dataline in aa:gmatch("(.-)\n") do table.insert(adat,dataline) end for dataline in ee:gmatch("(.-)\n") do table.insert(edat,dataline) end

if #sdat>0 and #sel~=#sdat then t_error("Line count for Style ["..#sdat.."] does not \nmatch the number of selected lines ["..#sel.."].") end if #adat>0 and #sel~=#adat then t_error("Line count for Actor ["..#adat.."] does not \nmatch the number of selected lines ["..#sel.."].") end if #edat>0 and #sel~=#edat then t_error("Line count for Effect ["..#edat.."] does not \nmatch the number of selected lines ["..#sel.."].") end

failt=0 if #sel~=#tdat and #sel>1 then failt=1 else for z,i in ipairs(sel) do line=subs[i] line.text=tdat[z] line.style=sdat[z] or line.style line.actor=adat[z] or line.actor line.effect=edat[z] or line.effect subs[i]=line end end if failt==1 then t_error("Line count of edited text does not \nmatch the number of selected lines.") clipboard.set(res.dat) end aegisub.set_undo_point(script_name) return sel end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function resc(str) str=str:gsub("[%%%(%)%[%]%.%*%-%+%?%^%$\\{}]","\\%1") return str end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function nRem(x) x=x:gsub("\n$","") return x end ulower=unicode.to_lower_case uupper=unicode.to_upper_case STAG="^{\\[^}]-}"

-- Re-Split doens't support inline tags (as that would make the functions 3 times as big) -- function resplitl(subs,sel,act) line=subs[act] text=line.text if act<#subs then nl=subs[act+1] nlt=nl.text:gsub("%b{}",""):gsub("^\\N","") first=nlt:match("^(%S+)\\N") or nlt:match("^%S+") if first then tags=nl.text:match(STAG) or "" nl.text=nl.text:gsub(STAG,""):gsub("^\\N","") :gsub("^%S+ *",function(f) if f:match("\\N") then return f:match("\\N(.*)") else return "" end end):gsub("^\\N","") nl.text=tags..nl.text text=text.."_!!!_"..first repeat text,r=text:gsub("({[^\\}]-})(_!!!_[^{ ]+)","%2%1") until r==0 text=text:gsub("_!!!_"," "):gsub("^({\\[^}]-}) *","%1") end subs[act+1]=nl end line.text=text subs[act]=line aegisub.set_undo_point(script_name) return sel end function resplitr(subs,sel,act) line=subs[act] text=line.text if act<#subs then nl=subs[act+1] ct=text:gsub("%b{}","") last=ct:match("\\N(%S+) *$") or ct:match("(%S+) *$") if last then text=text.."_!!!_" repeat text,r=text:gsub("(%b{})(_!!!_)","%2%1") until r==0 text=text:gsub(" *[^} ]+_!!!_",function(f) if f:match("\\N") then return f:match(" *(.*)\\N") else return "" end end) :gsub(" *{\\[^}]-}$","") tags=nl.text:match(STAG) or "" nl.text=tags..last.." "..nl.text:gsub(STAG,"") end subs[act+1]=nl end line.text=text subs[act]=line aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacros({ {script_name,script_description,editlines}, {"ReSplit/Backward","Resplits lines at a different place",resplitl}, {"ReSplit/Forward","Resplits lines at a different place",resplitr}, },false) else aegisub.register_macro(script_name,script_description,editlines) aegisub.register_macro("ReSplit/Backward","Resplits lines at a different place",resplitl) aegisub.register_macro("ReSplit/Forward","Resplits lines at a different place",resplitr) end -- Use the Help button for some basic info. -- Complete manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#multicopy script_name="MultiCopy" script_description="Copy and paste just about anything from/to multiple lines" script_author="unanimated" script_version="3.4" script_namespace="ua.MultiCopy" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.4.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end clipboard=require("aegisub.clipboard") re=require'aegisub.re'

-- COPY PART -- function copy(subs,sel) -- tags copytags="" for z,i in ipairs(sel) do progress("Copying from line: "..z.."/"..#sel) text=subs[i].text tags=text:match(STAG) or "" copytags=copytags..tags.."\n" if z==#sel then copytags=nRem(copytags) end end copydialog[2].label="" copydialog[3].value=copytags P=ADD(copydialog,cbut,{close='OK'}) if P=="Copy to clipboard" then clipboard.set(copytags) end end function copyt(subs,sel) -- text copytekst="" for z,i in ipairs(sel) do progress("Copying from line: "..z.."/"..#sel) text=subs[i].text text=text:gsub(STAG,"") if res.copymode=="visible text" then text=text:gsub("%b{}","") end copytekst=copytekst..text.."\n" if z==#sel then copytekst=nRem(copytekst) end end words=0 for t in copytekst:gmatch("%S+") do words=words+1 end copydialog[2].label="("..copytekst:len().." characters, "..words.." words)" copydialog[3].value=copytekst P=ADD(copydialog,cbut,{close='OK'}) if P=="Copy to clipboard" then clipboard.set(copytekst) end end function kopi(this) if this then cc=cc..this end end function copyc(subs,sel) -- any tags; layeractoreffectetc. cc="" tagg=res.dat:gsub("\n","") for z,i in ipairs(sel) do progress("Copying from line: "..z.."/"..#sel) line=subs[i] text=line.text vis=text:gsub("%b{}","") nospace=vis:gsub(" ","") comments="" for com in text:gmatch("{[^\\}]-}") do comments=comments..com end tagst=text:match(STAG) or "" tags=tagst:gsub("\\t%b()","")

if CM=="clip" then kopi(tags:match("\\i?clip%b()")) end if CM=="position" then kopi(tags:match("\\pos%b()")) end if CM=="blur" then kopi(tags:match("\\blur[%d%.]+")) end if CM=="border" then kopi(tags:match("\\bord[%d%.]+")) end if CM=="colour(s)" and res.c1 then kopi(tags:match("\\1?c&H%x+&")) end if CM=="colour(s)" and res.c2 then kopi(tags:match("\\2c&H%x+&")) end if CM=="colour(s)" and res.c3 then kopi(tags:match("\\3c&H%x+&")) end if CM=="colour(s)" and res.c4 then kopi(tags:match("\\4c&H%x+&")) end if CM=="alpha" and res.alf then kopi(tags:match("\\alpha&H%x+&")) end if CM=="alpha" and res.a1 then kopi(tags:match("\\1a&H%x+&")) end if CM=="alpha" and res.a2 then kopi(tags:match("\\2a&H%x+&")) end if CM=="alpha" and res.a3 then kopi(tags:match("\\3a&H%x+&")) end if CM=="alpha" and res.a4 then kopi(tags:match("\\4a&H%x+&")) end if CM=="fscx" then kopi(tags:match("\\fscx[%d%.]+")) end if CM=="fscy" then kopi(tags:match("\\fscy[%d%.]+")) end

if CM=="any tag" then for tag in tagg:gmatch("[^,]+") do tag=tag:gsub(" ","") tak=nil if tag=="t" then if tagst:match("\\t") then tak="" for t in tagst:gmatch("\\t%b()") do tak=tak..t end end elseif tag:match("^[abikps]$") then tak=tags:match("(\\"..tag.."%d*)[\\}]") elseif tag=="fs" then tak=tags:match("(\\fs%d*)[\\}]") elseif tag=="c" then tak=tags:match("(\\c&[^\\}]*)") elseif tag=="fad" then tak=tags:match("\\fad%b()") else tak=tags:match("(\\"..tag.."[^\\}]*)") end kopi(tak) end end

if CM=="layer" then cc=cc..line.layer end if CM=="actor" then cc=cc..line.actor end if CM=="effect" then cc=cc..line.effect end if CM=="style" then cc=cc..line.style end if CM=="duration" then cc=cc..line.end_time-line.start_time end if CM=="comments" then cc=cc..comments end if CM=="# of characters" then cc=cc..vis:len() end if CM=="# of chars (no space)" then cc=cc..nospace:len() end

if z~=#sel then cc=cc.."\n" end end copydialog[1].label="Data to export:" copydialog[2].label="" copydialog[3].value=cc P=ADD(copydialog,cbut,{close='OK'}) if P=="Copy to clipboard" then clipboard.set(cc) end end function copyall(subs,sel) -- all copylines="" for z,i in ipairs(sel) do progress("Copying from line: "..z.."/"..#sel) text=subs[i].text copylines=copylines..text.."\n" if z==#sel then copylines=nRem(copylines) end end words=0 for t in copylines:gmatch("%S+") do words=words+1 end copydialog[2].label="("..copylines:len().." characters, "..words.." words)" copydialog[3].value=copylines P=ADD(copydialog,cbut,{close='OK'}) if P=="Copy to clipboard" then clipboard.set(copylines) end end

-- CR Export for Pad -- function crmod(subs,sel) for i=1,#subs do progress("Crunching line: "..i.."/"..#subs) if subs[i].class=="dialogue" then line=subs[i] text=line.text style=line.style text=text :gsub("^ *","") :gsub(" +"," ") :gsub("{\\i0}$","")

-- change main style to Default style=style:gsub("[Ii]nternal","Italics") if style:match("[Ii]talics") and not style:match("[Ff]lashback") and not text:match("\\i1") then text="{\\i1}"..text end if style:match("[Mm]ain") or style:match("[Oo]verlap") or style:match("[Ii]talics") or style:match("[Ii]nternal") or style:match("[Ff]lashback") or style:match("[Nn]arrat") then style="Default" end

-- nuke tags from signs, set actor to "Sign", add timecode if not style:match("Defa") then text=text:gsub("{[^}]-}","") actor="Sign" timecode=math.floor(line.start_time/1000) tc1=math.floor(timecode/60) tc2=timecode%60+1 if tc2==60 then tc2=0 tc1=tc1+1 end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end text="{TS "..tc1..":"..tc2.."}"..text if style:match("[Tt]itle") then text=text:gsub("({TS %d%d:%d%d)}","%1 Title}") end

else text=text:gsub(" *\\[Nn] *"," ") :gsub("\\a6","\\an8") line.text=text end line.actor="" line.style=style line.text=text subs[i]=line end end -- move signs to the top of the script progress("Sorting") i=1 moved=0 while i<=(#subs-moved) do line=subs[i] if line.class=="dialogue" and line.style=="Default" then subs.delete(i) moved=moved+1 subs.append(line) else i=i+1 end end -- copy text from all lines copylines="" for i=1,#subs do progress("Copying from line: "..i.."/"..#subs) if subs[i].class=="dialogue" then line=subs[i] text=line.text copylines=copylines..text.."\n" if i==#subs then copylines=nRem(copylines) end subs[i]=line end end words=0 for t in copylines:gmatch("%S+") do words=words+1 end copydialog[2].label="("..copylines:len().." characters, "..words.." words)" copydialog[3].value=copylines P=ADD(copydialog,cbut,{close='OK'}) if P=="Copy to clipboard" then clipboard.set(copylines) end end

-- COPY BETWEEN COLUMNS -- function copycol(subs,sel) if res.attach then atgui={ {x=1,y=1,class="edit",name="merge"}, {x=0,y=0,class="label",label="Before / "}, {x=1,y=0,class="checkbox",name="after",label="After"}, {x=0,y=1,class="label",label="Link: "} } pres,rez=ADD(atgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end merge=rez.merge end for z,i in ipairs(sel) do line=subs[i] source=line[res.copyfrom] target=line[res.copyto] if type(target)=="number" then data=tonumber(source) else data=source end if data then if res.attach then if rez.after then data=target..merge..data else data=data..merge..target end end line[res.copyto]=data end if res.switch and not res.attach then if type(source)=="number" then data2=tonumber(target) else data2=target end if data2 then line[res.copyfrom]=data2 end end subs[i]=line end end

-- PASTE PART -- function paste(subs,sel) -- tags data={} raw=res.dat.."\n" loop=nil over=nil for dataline in raw:gmatch("(.-)\n") do table.insert(data,dataline) end if #data~=#sel then mispaste(sel) end overloop(subs,sel) for z,i in ipairs(sel) do if data[z] then line=subs[i] text=line.text text=text:gsub(STAG,"") text=data[z]..text line.text=text subs[i]=line end end end function pastet(subs,sel) -- text data={} raw=res.dat.."\n" loop=nil over=nil for dataline in raw:gmatch("(.-)\n") do table.insert(data,dataline) end if #data~=#sel then mispaste(sel) end overloop(subs,sel) for z,i in ipairs(sel) do if data[z] then line=subs[i] text=line.text tags=text:match(STAG) or "" text=text:gsub(esc(tags),"") txt2=data[z] matchfail=false -- using orig. line's inline tags if text:match(" {\\[^}]*}%w%w+") and not txt2:match(ATAG) then -- match same words if found for tgs,wrd in text:gmatch(" ({\\[^}]*})(%w%w+)") do if txt2:match("^"..wrd.."%W") or txt2:match(" "..wrd.."%W") or txt2:match(" "..wrd.."$") then txt2=txt2:gsub(wrd,tgs..wrd) else matchfail=true end end -- apply tags by word count if txt2==data[z] or matchfail then txt2=data[z] text=text:gsub("{[^\\}]-}","") words=0 tagtab={} for w in text:gmatch("%S+") do words=words+1 tag1=w:match(STAG) tag2=w:match(ATAG.."$") tagtab[words]={t1=tag1,t2=tag2} end wrds2=0 txt3="" for w in txt2:gmatch("%S+") do wrds2=wrds2+1 if tagtab[wrds2] and tagtab[wrds2].t1 then w=tagtab[wrds2].t1..w end if tagtab[wrds2] and tagtab[wrds2].t2 then w=w..tagtab[wrds2].t2 end txt3=txt3..w.." " end txt2=txt3:gsub(" $","") end end text=tags..txt2 text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") line.text=text subs[i]=line end end end function gbctext(text,text2) text=text:gsub("\\c&","\\1c&") stags=text:match(STAG) or "" lastag=text:match("("..ATAG..").$") ntext=text2:gsub("(.)$",lastag.."%1") if lastag:match("\\[1234]c") then textseq={} ltrs=re.find(text2,".") for l=1,#ltrs do table.insert(textseq,ltrs[l].str) end tagfirst={} taglast={} sc1="\\c"..styleref.color1:gsub("H%x%x","H") sc2="\\2c"..styleref.color2:gsub("H%x%x","H") sc3="\\3c"..styleref.color3:gsub("H%x%x","H") sc4="\\4c"..styleref.color4:gsub("H%x%x","H") stylecol={sc1,sc2,sc3,sc4} for c=1,4 do cfirst=stags:match("\\"..c.."c&H%x+&") clast=lastag:match("\\"..c.."c&H%x+&") if cfirst==nil and clast~=nil then cfirst=stylecol[c] end if cfirst~=nil and clast~=nil then table.insert(tagfirst,cfirst) table.insert(taglast,clast) end end grtext=textseq[1] tsn=#textseq for c=2,tsn-1 do coltag="" if textseq[c]~=" " then for t=1,#taglast do col1=tagfirst[t] col2=taglast[t] B1,G1,R1=col1:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=col2:match("(%x%x)(%x%x)(%x%x)") nR1=(tonumber(R1,16)) nR2=(tonumber(R2,16)) nG1=(tonumber(G1,16)) nG2=(tonumber(G2,16)) nB1=(tonumber(B1,16)) nB2=(tonumber(B2,16)) Rdiff=(nR2-nR1)/tsn R=nR1+Rdiff*(c-1) Gdiff=(nG2-nG1)/tsn G=nG1+Gdiff*(c-1) Bdiff=(nB2-nB1)/tsn B=nB1+Bdiff*(c-1) R=tohex(round(R)) G=tohex(round(G)) B=tohex(round(B)) coltag=coltag..col1:gsub("(%x%x)(%x%x)(%x%x)",B..G..R) end grtext=grtext.."{*"..coltag.."}"..textseq[c] else grtext=grtext.." " end end ntext=grtext..lastag..textseq[tsn] end text=stags..ntext return text end

-- pasted data / selection mismatch -- function mispaste(sel) if raw=="\n" or raw=="" then t_error("No data provided.",1) end mispastegui={{class="label",label="Selected lines: "..#sel.." ... Pasted lines: "..#data}} if #data<#sel then B1="Loop paste" B2="Paste only "..#data.." lines" else B1="Paste only "..#sel.." lines" B2="Paste all "..#data.." lines" end B1=B1:gsub(" 1 lines"," 1 line") B2=B2:gsub(" 1 lines"," 1 line") P=ADD(mispastegui,{B1,B2,"Cancel"},{close='Cancel'}) if P=="Cancel" then ak() end if P=="Loop paste" then loop=true end if P=="Paste all "..#data.." lines" then over=true end end function overloop(subs,sel) if over then ldiff=#data-#sel last=#sel for a=1,ldiff do if sel[last]+a<=#subs then table.insert(sel,sel[last]+a) end end end if loop then y=1 maxd=#data repeat table.insert(data,data[y]) y=y+1 if y==maxd+1 then y=1 end until #data>=#sel end end

-- MAIN PASTE PART -- function pastec(subs,sel) pasteover=0 podetails="" loop=nil over=nil data={} raw=res.dat.."\n" raw=raw:gsub("\n\n$","\n") for dataline in raw:gmatch("(.-)\n") do table.insert(data,dataline) end if PM=="superpasta" or PM=="all" or PM=="pasteover+" then special=true else special=false end if #data~=#sel and not special then mispaste(sel) end

-- PASTE OVER WITH CHECK if PM=="all" then pasteover=1 pasterep="" pnum="" m100=0 m0=0 mtotal=0 om=0 omtotal=0 alter="Default" for i=1,#subs do if subs[i].class=="style" and subs[i].name:match("Alt") then alter=subs[i].name end if subs[i].class=="dialogue" then z0=i-1 break end end susp="" suspL=0 suspT=0 sustab={} for z,i in ipairs(sel) do line=subs[i] T1l=line.text T2l=data[z] or "" T2l=T2l:gsub("^%w+:(%S)","%1") T1=T1l:gsub("%b{}","") :gsub(" ?\\N"," ") T2=T2l:gsub("%b{}","") :gsub(" ?\\N"," ") L1=T1:len() L2=T2:len() ln=i-z0 if ln<10 then ln="00"..ln elseif ln<100 then ln="0"..ln end

-- comparing words between current and pasted TC=T1:gsub("[%.,%?!\":;%—]","") TD=T2:gsub("[%.,%?!\":;%—]","") ml="" for c in TC:gmatch("[%w']+") do for d in TD:gmatch("[%w']+") do if c:lower()==d:lower() then TD=TD:gsub("^.-"..d,"") ml=ml..c.." " break end end end ml=ml:gsub(" $","") M1=ml:len() M2=TC:len() if M2==0 then M2=1 end match1=math.floor((M1*100/M2+0.5)) if M1==0 and M2==1 then match1=100 end

-- other direction TC=T1:gsub("[%.,%?!\":;%—]","") TD=T2:gsub("[%.,%?!\":;%—]","") mr="" for c in TD:gmatch("[%w']+") do for d in TC:gmatch("[%w']+") do if c:lower()==d:lower() then TC=TC:gsub("^.-"..d,"") mr=mr..c.." " break end end end mr=mr:gsub(" $","") M1=mr:len() M2=TD:len() if M2==0 then M2=1 end match2=math.floor((M1*100/M2+0.5)) if M1==0 and M2==1 then match2=100 end

if match1>match2 then match=match1 othermatch=match2 ma=ml else match=match2 othermatch=match1 ma=mr end pasterep=pasterep..ln.." "..match.."% "..ma.."\n" if match==100 then m100=m100+1 if othermatch==100 then om=om+1 end end if match==0 then m0=m0+1 end mtotal=mtotal+match omtotal=omtotal+othermatch line.effect=line.effect.."["..othermatch.."%]" line.text=T2l if T2l:match("{%*ALT%*}") then line.style=alter end if T2l:match("^%#") then line.comment=true end subs[i]=line end

if #sel~=#data then for s=1,#sustab do susp=susp..sustab[s] ..", " end susp=susp:gsub(", $","") ldiff=#sel-#data if math.abs(ldiff)==1 then es="" else es="s" end if ldiff>0 then LD="The pasted data is "..ldiff.." line"..es.." shorter than your selection" else LD="The pasted data is ".. 0-ldiff.." line"..es.." longer than your selection" end podetails="Line count of the selection doesn't match pasted data.\n"..LD.."\nIf you're pasting over edited text from a pad,\nwhere you start getting too many 0% is where it's probably a line off." else fullm=math.floor(m100*100/#sel+0.5) zerom=math.floor(m0*100/#sel+0.5) totalm=math.floor(mtotal*10/#sel+0.5)/10 otherm=math.floor(om*100/#sel+0.5) totalom=math.floor(omtotal*10/#sel+0.5)/10 podetails="Line count of the selection matched pasted data. ("..#sel.." lines)\nFull match: "..m100.."/"..#sel.." ("..fullm.."%) Both ways: "..om.."/"..#sel.." ("..otherm.."%, ie. "..#sel-om.." lines changed - ".. 100- otherm.."%)\nZero match: "..m0.."/"..#sel.." ("..zerom.."%)\nOverall match: "..totalm.."% (".. 100-totalm.."% change / ".. 100-totalom.."% both ways)" end end -- of paste over with check

-- PASTEOVER+ if PM=="pasteover+" then rez=nil finished=nil tada={} pasted={} LL="" RR="" MIS="!!! --- Line count mismatch: " for z,i in ipairs(sel) do table.insert(tada,subs[i].text) end for i=1,13 do LL=LL..tada[i].."\n" RR=RR..data[i].."\n" end LL=nRem(LL) RR=nRem(RR) zl=13 zr=13 POG={ {x=0,y=0,width=2,class="label",label="Original text. Don't modify this. Number of lines to be sent with each 'Send':"}, {x=3,y=0,width=3,class="label",label="Pasted text. Modify this so that the lines you're sending on both sides match. Numbers at the bottom = lines to add."}, {x=3,y=1,width=3,height=20,class="textbox",name="rite",value=RR}, {x=0,y=1,width=3,height=20,class="textbox",name="left",value=LL}, {x=2,y=0,class="intedit",name="send",value=5,min=1}, {x=0,y=21,class="intedit",name="a1",value=AL or 10,min=1}, {x=3,y=21,class="intedit",name="a2",value=AR or 10,min=1}, {x=1,y=21,width=2,class="edit",name="i1",value=#tada.." lines total in original script. "..#tada-13 .." remaining."}, {x=4,y=21,width=2,class="edit",name="i2",value=#data.." lines total in pasted data. "..#data-13 .." remaining."}, } POP="" repeat POP,rez=ADD(POG,{"Add Left","Add Both","Send Next and Add Both","Send Next","Add Right","Unsend","Cancel"}, {ok='Send Next',close='Cancel'}) AL=rez.a1 AR=rez.a2 LL=rez.left RR=rez.rite -- Send sent=true if POP:match("Send Next") then S=0 sent=nil _,brl=LL:gsub("\n","") _,brr=RR:gsub("\n","") if brr~=brl then logL=MIS..brl+1 .." lines here. (End of script.) --- !!!" logR=MIS..brr+1 .." lines here. (End of pasted data.) --- !!!" end -- Not Enough on Left if brl<(rez.send) then if zl<#tada then S=1 logL="!!! --- You don't have "..rez.send.." lines to send. Add more lines. "..#tada-zl.." remaining. --- !!!" end if zl==#tada and zr<#data then S=1 logL="--- End of script. Some pasted data remaining. ---" end if zl==#tada and zr==#data and brr~=brl then S=1 end elseif zr==#data and zl<#tada then logL=MIS..brl+1 .." lines here. "..#tada-zl.." more to add. --- !!!" end -- Not Enough on Right if brr<(rez.send) then if zr<#data then S=1 logR="!!! --- You don't have "..rez.send.." lines to send. Add more lines. "..#data-zr.." remaining. --- !!!" end if zr==#data and zl<#tada then S=1 logR="--- End of pasted data. Some lines in the script remaining. ---" end if zr==#data and zl==#tada and brr~=brl then S=1 end elseif zl==#tada and zr<#data then logR=MIS..brr+1 .." lines here. "..#data-zr.." more to add. --- !!!" end -- sending -- if S==0 then LL=LL.."\n" RR=RR.."\n" for l=1,rez.send do table.insert(pasted,RR:match("^(.-)\n")) RR=RR:gsub("^.-\n","") LL=LL:gsub("^.-\n","") end LL=nRem(LL) RR=nRem(RR) if #tada==zl and #data==zr and LL=="" and RR=="" then finished=true end sent=true end end -- Add Left if sent and POP:match("Add Left") or sent and POP:match("Add Both") then if LL~="" then LL=LL.."\n" end for i=zl+1,AL+zl do if tada[i] then LL=LL..tada[i].."\n" zl=zl+1 end end LL=nRem(LL) end -- Add Right if sent and POP:match("Add Right") or sent and POP:match("Add Both") then if RR~="" then RR=RR.."\n" end for i=zr+1,AR+zr do if data[i] then RR=RR..data[i].."\n" zr=zr+1 end end RR=nRem(RR) end if sent then logL=#tada-zl.." lines remaining in original script." logR=#data-zr.." lines remaining in pasted data." if zl==#tada then logL="--- End of script. ---" end if zr==#data then logR="--- End of data. ---" end end -- Unsend if POP=="Unsend" then for u=#pasted,#pasted-rez.send+1,-1 do LL=tada[u].."\n"..LL RR=pasted[u].."\n"..RR table.remove(pasted,u) end end -- rebuild GUI if rez then for k,v in ipairs(POG) do if v.name=="left" then v.value=LL end if v.name=="rite" then v.value=RR end if v.name=="i1" then v.value=logL end if v.name=="i2" then v.value=logR end if v.class=="intedit" then v.value=rez[v.name] end end end until POP=="Cancel" or finished

if POP=="Cancel" then ak() end for z,i in ipairs(sel) do line=subs[i] line.text=pasted[z] subs[i]=line end end -- of pasteover+

-- SUPER PASTA - advanced paste over if PM=="superpasta" then styles="" warningstyles="" for i=1,#subs do if subs[i].class=="style" then styles=styles..","..subs[i].name.."," end end if #data<10 then spg=#data else spg=10 end saet={"style","actor","effect","text"} seaet={"start time","end time","actor","effect","text"} lrvlaet={"L","R","V","layer","actor","effect","text"} spgui={ {x=0,y=0,class="checkbox",name="clay",label="layer"}, {x=1,y=0,class="checkbox",name="cstart",label="start time"}, {x=2,y=0,class="checkbox",name="cendt",label="end time"}, {x=3,y=0,class="checkbox",name="cstyle",label="style"}, {x=4,y=0,class="checkbox",name="cactor",label="actor"}, {x=5,y=0,class="checkbox",name="ceffect",label="effect"}, {x=6,y=0,class="checkbox",name="cL",label="L"}, {x=7,y=0,class="checkbox",name="cR",label="R"}, {x=8,y=0,class="checkbox",name="cV",label="V"}, {x=9,y=0,class="checkbox",name="ctext",label="text"}, {x=10,y=0,class="label",label="<-- check columns to use"},

{x=0,y=spg+1,class="dropdown",name="dlay",value="layer",items={"layer","L","R","V","style","actor","effect","text" }}, {x=1,y=spg+1,class="dropdown",name="dstart",value="start time",items=seaet}, {x=2,y=spg+1,class="dropdown",name="dendt",value="end time",items=seaet}, {x=3,y=spg+1,class="dropdown",name="dstyle",value="style",items=saet}, {x=4,y=spg+1,class="dropdown",name="dactor",value="actor",items=saet}, {x=5,y=spg+1,class="dropdown",name="deffect",value="effect",items=saet}, {x=6,y=spg+1,class="dropdown",name="dL",value="L",items=lrvlaet}, {x=7,y=spg+1,class="dropdown",name="dR",value="R",items=lrvlaet}, {x=8,y=spg+1,class="dropdown",name="dV",value="V",items=lrvlaet}, {x=9,y=spg+1,class="dropdown",name="dtext",value="text",items=saet}, {x=10,y=spg+1,class="label",label="<-- columns to apply to"}, {x=0,y=spg+2,width=10,class="label",label="Paste over selected columns or copy the content of one column to another. (GUI shows only first 10 lines for reference.)"}, } lines={} for li=1,#data do dtext=data[li] if not dtext:match("^Dialogue") and not dtext:match("^Comment") then ADD({{class="label",label="»"..dtext.."\nNot a valid/complete dialogue line."}},{"OK"},{close='OK'}) ak() end dline=string2line(dtext) table.insert(lines,dline) end for d=1,spg do dtext=data[d] dline=lines[d] table.insert(spgui,{x=0,y=d,class="label",name="lay"..d,label=dline.layer}) table.insert(spgui,{x=1,y=d,class="label",name="start"..d,label=dline.start_time}) table.insert(spgui,{x=2,y=d,class="label",name="endt"..d,label=dline.end_time}) table.insert(spgui,{x=3,y=d,class="label",name="style"..d,label=dline.style}) table.insert(spgui,{x=4,y=d,class="label",name="actor"..d,label=dline.actor}) table.insert(spgui,{x=5,y=d,class="label",name="effect"..d,label=dline.effect}) table.insert(spgui,{x=6,y=d,class="label",name="margl"..d,label=dline.margin_l}) table.insert(spgui,{x=7,y=d,class="label",name="margr"..d,label=dline.margin_r}) table.insert(spgui,{x=8,y=d,class="label",name="margt"..d,label=dline.margin_t}) table.insert(spgui,{x=9,y=d,width=25,class="edit",name="text"..d,value=dline.text}) end -- run gui repeat if press=="Check all" then for key,val in ipairs(spgui) do if val.class=="checkbox" then val.value=true end end end if press=="Uncheck all" then for key,val in ipairs(spgui) do if val.class=="checkbox" then val.value=false end end end press,rez=ADD(spgui,{"OK","Check all","Uncheck all","Cancel"},{ok='OK',close='Cancel'}) until press~="Check all" and press~="Uncheck all" if press=="Cancel" then ak() end

-- Apply pasteover for z,i in ipairs(sel) do line=subs[i] if lines[z]~=nil then if rez.clay then target=rez.dlay source=lines[z].layer line=shoot(line) end if rez.cstart then target=rez.dstart source=lines[z].start_time line=shoot(line) end if rez.cendt then target=rez.dendt source=lines[z].end_time line=shoot(line) end if rez.cstyle then target=rez.dstyle source=lines[z].style line=shoot(line) end if rez.cactor then target=rez.dactor source=lines[z].actor line=shoot(line) end if rez.ceffect then target=rez.deffect source=lines[z].effect line=shoot(line) end if rez.cL then target=rez.dL source=lines[z].margin_l line=shoot(line) end if rez.cR then target=rez.dR source=lines[z].margin_r line=shoot(line) end if rez.cV then target=rez.dV source=lines[z].margin_t line=shoot(line) end if rez.ctext then target=rez.dtext source=lines[z].text line=shoot(line) end end subs[i]=line end end -- of superpasta

-- PASTE ANY TAG -- if not special then overloop(subs,sel) warningstyles="" for z,i in ipairs(sel) do if data[z] then line=subs[i] text=line.text text2=data[z] if not text:match("^{\\") then text="{\\mc}"..text end if PM=="any tag" then if text:match("^{[^}]-\\t") then text=text:gsub("^({[^}]-)\\t","%1"..text2.."\\t") else text=text:gsub("^({\\[^}]-)}","%1"..text2.."}") end end if PM=="layer" then line.layer=text2 end if PM=="actor" then line.actor=text2 end if PM=="effect" then line.effect=text2 end if PM=="style" then sr=stylechk(subs,text2) if not sr and not warningstyles:match(","..esc(text2)..",") then warningstyles=warningstyles..","..text2.."," end line.style=text2 end if PM=="duration" then line.end_time=line.start_time+text2 end if PM=="comments" then text2=text2:gsub("^([^{])(.*)([^}])$","{%1%2%3}") text=text..text2 end if PM=="text mod." then text=textmod2(text2) end if PM=="gbc text" then styleref=stylechk(subs,line.style) text=gbctext(text,text2) end if PM=="de-irc" then line=string2line(text2) text=line.text end text=text :gsub(ATAG,function(tg) tg=duplikill(tg) tg=extrakill(tg,2) return tg end) :gsub("\\mc","") :gsub("{+}+","") line.text=text subs[i]=line end end end

if warningstyles and warningstyles~="" then warningstyles=warningstyles:gsub(",,",", ") :gsub("^,","") :gsub(",$","") ADD({{class="label",label="Warning! These styles don't exist: "..warningstyles}},{"OK"},{close='OK'}) end

if pasteover==1 then pr,rs=ADD({ {width=40,class="label",name="ch1",label="line % matched matched words"}, {x=0,y=1,width=40,height=18,class="textbox",name="ch2",value=pasterep}, {x=0,y=19,width=40,height=4,class="textbox",name="ch3",value=podetails}, {x=0,y=23,width=40,class="checkbox",name="ef",label="% in effect",value=false}, },{"OK"},{close='OK'}) if not rs.ef then for z,i in ipairs(sel) do l=subs[i] l.effect=l.effect:gsub("%[%d+%%%]","") subs[i]=l end end end end function shoot(line) if target=="layer" then line.layer=source end if target=="start time" then line.start_time=source end if target=="end time" then line.end_time=source end if target=="style" then line.style=source if source=="" then source="[unnamed style]" end if not styles:match(","..esc(source)..",") and not warningstyles:match(","..esc(source)..",") then warningstyles=warningstyles..","..source.."," end end if target=="actor" then line.actor=source end if target=="effect" then line.effect=source end if target=="L" then line.margin_l=source end if target=="R" then line.margin_r=source end if target=="V" then line.margin_t=source end if target=="text" then line.text=source end return line end

-- paste text over while keeping tags -- function textmod2(text2) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 vis=text2:gsub("%b{}","") ltrs=re.find(vis,".") for l=1,#ltrs do table.insert(tk,ltrs[l].str) end stags=text:match(STAG) or "" text=text:gsub(STAG,"") count=0 for seq in text:gmatch("[^{]-"..ATAG) do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos then ps=#pos+count else ps=0+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n,t in ipairs(tg) do if t.p==i then newt=newt..t.t as=t.a end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext=stags..newline text=newtext:gsub("{}","") return text end

-- reanimatools -- function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function string2line(str) local ltype,layer,s_time,e_time,style,actor,margl,margr,margv,eff,txt=str:match("(%a+): (%d+),([^,]-),([^,]-),([^,]- ),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),(.*)") l2={} l2.class="dialogue" if ltype=="Comment" then l2.comment=true else l2.comment=false end l2.layer=layer l2.start_time=string2time(s_time) l2.end_time=string2time(e_time) l2.style=style l2.actor=actor l2.margin_l=margl l2.margin_r=margr l2.margin_t=margv l2.effect=eff l2.text=txt l2.extra={} return l2 end function string2time(timecode) if timecode==nil then t_error("Invalid timecode.",123) end timecode=timecode:gsub("(%d):(%d%d):(%d%d)%.(%d%d)",function(a,b,c,d) return d*10+c*1000+b*60000+a*3600000 end) return timecode end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function nRem(x) x=x:gsub("\n$","") return x end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end cbut={"OK","Copy to clipboard"} copydialog= {{class="label",label="Text to export:"}, {x=1,width=49,class="label"}, {y=1,width=50,height=20,class="textbox",name="copytext"}}

-- GUI PART function multicopy(subs,sel) ADD=aegisub.dialog.display ak=aegisub.cancel ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" copytab={"tags","text","visible text","all","------","export CR for pad","------","any tag","clip","position","blur","border","colour(s)","alpha","fscx","fscy","------","layer","duration","actor","effect","style","comments","# of characters","# of chars (no space)"} pastab={"all","pasteover+","any tag","superpasta","gbc text","text mod.","de-irc","------","layer","duration","actor","effect","style","comments"} fields={"style","actor","effect","text","layer","start_time","end_time","margin_l","margin_r","margin_t"} gui={ {x=0,y=19,class="label",label="Copy:"}, {x=1,y=19,width=3,class="dropdown",name="copymode",value=CM or "tags",items=copytab}, {x=4,y=19,class="label",label="Paste extra:"}, {x=5,y=19,width=2,class="dropdown",name="pastemode",value=PM or "all",items=pastab}, {x=0,y=0,width=12,height=17,class="textbox",name="dat"},

{x=0,y=17,width=2,class="checkbox",name="col",label="Copy from",hint="Copy from one column to another"}, {x=2,y=17,width=2,class="dropdown",name="copyfrom",value=CF or "actor",items=fields}, {x=4,y=17,class="label",label=" to"}, {x=5,y=17,width=2,class="dropdown",name="copyto",value=CT or "effect",items=fields}, {x=7,y=17,width=2,class="checkbox",name="switch",label="Switch",hint="Switch the content of selected columns"}, {x=9,y=17,class="checkbox",name="attach",label="Attach",hint="Attach content from one column to that of another"},

{x=0,y=18,class="checkbox",name="c1",label="\\c",value=true}, {x=1,y=18,class="checkbox",name="c2",label="\\2c"}, {x=2,y=18,class="checkbox",name="c3",label="\\3c"}, {x=3,y=18,class="checkbox",name="c4",label="\\4c"}, {x=4,y=18,class="checkbox",name="alf",label="\\alpha",value=true}, {x=5,y=18,class="checkbox",name="a1",label="\\1a"}, {x=6,y=18,class="checkbox",name="a2",label="\\2a"}, {x=7,y=18,class="checkbox",name="a3",label="\\3a"}, {x=8,y=18,width=2,class="checkbox",name="a4",label="\\4a"},

{x=10,y=17,class="checkbox",name="rpt",label="Repeat last"}, {x=10,y=18,class="label",label="Replace this..."}, {x=11,y=18,class="label",label="...with this."}, {x=9,y=19,class="label",label="Replacer:"}, {x=10,y=19,class="edit",name="rep1",value=lastrep1 or ""}, {x=11,y=19,class="edit",name="rep2",value=lastrep2 or ""}, {x=11,y=17,class="label",label="MultiCopy version "..script_version} } buttons={"Copy","Paste tags","Paste text","Paste extra","Paste from clipboard","Replace","Help","Cancel"} repeat if P=="Paste from clipboard" then klipboard=clipboard.get() for key,val in ipairs(gui) do if val.name=="dat" then val.value=klipboard else val.value=res[val.name] end end end if P=="Help" then herp=[[ COPY part copies specified things line by line. PASTE part pastes these things line by line. The main idea is to copy something from X lines and paste it to another X lines. 'Copy from-to' is a quick copy function between columns. 'Switch' switches them. Copying strings to number fields does nothing. 'Attach' adds the copied value to the target value by combining them. tags = initial tags; text = text AFTER initial tags (will include inline tags) all = tags+text, ie. everything in the Text field any tag = copies whatever tag(s) you specify by typing in this field, like "org", "fad", "t", or "blur,c,alpha". export CR for pad: signs go to top with {TS} timecodes; nukes linebreaks and other CR garbage, fixes styles, etc.

Paste part: all: this is like regular paste over from a pad, but with checks to help identify where stuff breaks if the line count is different or shifted somewhere. If you're pasting over a script that has different line splitting than it should, this will show you pretty reliably where the discrepancies are. pasteover+ goes even a bit further. text mod.: this pastes over text while keeping inline tags. If your line is {\t1}a{\t2}b{\t3}c and you paste "def", you will get {\t1}d{\t2}e{\t3}f. This simply counts characters, so if you paste "defgh", you get {\t1}d{\t2}e{\t3}fgh, and for "d", you get {\t1}d. Comments get nuked. gbc text: for pasting over lines with gradient by character. You get this: [initial tags][pasted text without last character][tag that was before last character][last character of pasted text] For colours, the gradient should be replicated in full. de-irc: paste straight from irc with timecodes and nicknames, and stuff gets parsed correctly. If pasted data doesn't match line count of selection, you get choices as for what you want to do.

You can use Replacer on pasted data: copy 'bord', replace 'bord' with 'shad', and paste border values as shadow values.]] for k,v in ipairs(gui) do if v.name=="dat" then v.value=herp else v.value=res[v.name] end end end if P=="Replace" then for k,v in ipairs(gui) do if v.name=="dat" then v.value=res[v.name]:gsub(esc(res.rep1),res.rep2) else v.value=res[v.name] end end end P,res=ADD(gui,buttons,{close='Cancel'}) until P~="Paste from clipboard" and P~="Help" and P~="Replace" if P=="Cancel" then ak() end if res.rpt and lastres then res=lastres end CM=res.copymode PM=res.pastemode CF=res.copyfrom CT=res.copyto lastres=res if P=="Copy" then if res.col then copycol(subs,sel) else if res.copymode=="tags" then copy(subs,sel) elseif res.copymode:match("text") then copyt(subs,sel) elseif res.copymode=="all" then copyall(subs,sel) elseif res.copymode=="export CR for pad" then crmod(subs,sel) else copyc(subs,sel) end end end if P=="Paste tags" then paste(subs,sel) end if P=="Paste text" then pastet(subs,sel) end if P=="Paste extra" then pastec(subs,sel) end aegisub.set_undo_point(script_name.." - "..P) return sel end if haveDepCtrl then depRec:registerMacro(multicopy) else aegisub.register_macro(script_name,script_description,multicopy) end script_name="Significance" script_description="Import stuff, number stuff, chapter stuff, replace stuff, do a significant amount of other stuff to stuff." script_author="unanimated" script_url1="http://unanimated.xtreemhost.com/ts/import.lua" script_url2="https://raw.githubusercontent.com/unanimated/luaegisub/master/import.lua" script_version="3.0" script_namespace="ua.Significance" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end clipboard=require("aegisub.clipboard") re=require'aegisub.re'

-- IMPORT/EXPORT ------function important(subs,sel,act) aline=subs[act] atext=aline.text atags=atext:match("^{(\\[^}]-)}") or "" atags=atags:gsub("\\move%b()","") atxt=atext:gsub("^{\\[^}]-}","") -- create table from user data (lyrics) sdata={} if res.mega=="update lyrics" and res.dat=="" then t_error("No lyrics given.",true) else res.dat=res.dat.."\n" for dataline in res.dat:gmatch("(.-)\n") do if dataline~="" then table.insert(sdata,dataline) end end end

-- user input sub1=res.rep1 sub2=res.rep2 sub3=res.rep3 zer=res.zeros rest=res.rest

-- this checks whether the pattern for lines with lyrics was found songcheck=0

-- paths scriptpath=ADP("?script") if script_path=="relative" then path=scriptpath.."\\"..relative_path end if script_path=="absolute" then path=absolute_path end

-- IMPORT -- if res.mega:match("import") and not res.mega:match("chptrs") then

noshift=false defect=false keeptxt=false deline=false

-- import-single-sign GUI if res.mega=="import sign" then press,reslt=ADD({ {x=0,y=0,width=1,height=1,class="label",label="File name:"}, {x=0,y=1,width=2,height=1,class="edit",name="signame"},

{x=1,y=0,width=2,height=1,class="dropdown",name="signs",items={"title","eptitle","custom","eyecatch"},value="cust om"}, {x=2,y=1,width=1,height=1,class="label",label=".ass"}, {x=0,y=2,width=3,height=1,class="checkbox",name="matchtime",label="keep current line's times",value=true,}, {x=0,y=3,width=3,height=1,class="checkbox",name="keeptext",label="keep current line's text",value=false,}, {x=0,y=4,width=3,height=1,class="checkbox",name="keeptags",label="combine tags (current overrides) ",value=false,}, {x=0,y=5,width=3,height=1,class="checkbox",name="addtags",label="combine tags (imported overrides)",value=false,}, {x=0,y=6,width=3,height=1,class="checkbox",name="noshift",label="don't shift times (import as is)",value=false,}, {x=0,y=7,width=3,height=1,class="checkbox",name="deline",label="delete original line",value=false,}, },{"OK","Cancel"},{ok='OK',close='Cancel'}) if press=="Cancel" then ak() end if reslt.signs=="custom" then signame=reslt.signame else signame=reslt.signs end noshift=reslt.noshift keeptxt=reslt.keeptext deline=reslt.deline keeptags=reslt.keeptags addtags=reslt.addtags end

-- read signs.ass if res.mega=="import signs" then file=io.open(path.."signs.ass") if file==nil then ADD({{x=0,y=0,width=1,height=1,class="label",label=path.."signs.ass\nNo such file."}},{"ok"},{cancel='ok'}) ak() end signs=file:read("*all") io.close(file) end

-- sort out if using OP, ED, signs, or whatever .ass and read the file songtype=res.mega:match("import (%a+)") if songtype=="sign" then songtype=signame end file=io.open(path..songtype..".ass") if file==nil then t_error(path..songtype..".ass\nNo such file.",true) end song=file:read("*all") io.close(file)

-- cleanup useless stuff song=song:gsub("^.-(Dialogue:)","%1") song=song.."\n" song=song:gsub("\n\n$","\n") song=song:gsub("%[[^%]]-%]\n","\n") -- make table out of lines slines={} for sline in song:gmatch("(.-)\n") do if sline~="" then table.insert(slines,sline) end end -- save (some) current line properties btext=atext basetime=aline.start_time basend=aline.end_time basestyle=aline.style baselayer=aline.layer

-- import-signs list and GUI if res.mega=="import signs" then -- make a table of signs in signs.ass signlist={} signlistxt="" for x=1,#slines do efct=slines[x]:match("%a+: %d+,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-(,[^,]-,).*") --aegisub.log("\n efct "..efct) esfct=esc(efct) if not signlistxt:match(esfct) then signlistxt=signlistxt..efct end end for sn in signlistxt:gmatch(",([^,]-),") do table.insert(signlist,sn) end -- import-signs GUI button,reslt=ADD({ {x=0,y=0,width=1,height=1,class="label",label="Choose a sign to import:"}, {x=0,y=1,width=1,height=1,class="dropdown",name="impsign",items=signlist,value=signlist[1]}, {x=0,y=2,width=1,height=1,class="checkbox",name="matchtime",label="keep current line's times",value=true,}, {x=0,y=3,width=1,height=1,class="checkbox",name="keeptext",label="keep current line's text",value=false,}, {x=0,y=4,width=1,height=1,class="checkbox",name="keeptags",label="combine tags (current overrides) ",value=false,}, {x=0,y=5,width=1,height=1,class="checkbox",name="addtags",label="combine tags (imported overrides)",value=false,}, {x=0,y=6,width=1,height=1,class="checkbox",name="noshift",label="don't shift times (import as is)",value=false,}, {x=0,y=7,width=1,height=1,class="checkbox",name="defect",label="delete 'effect'",value=false,}, {x=0,y=8,width=1,height=1,class="checkbox",name="deline",label="delete original line",value=false,}, },{"OK","Cancel"},{ok='OK',close='Cancel'}) if button=="Cancel" then ak() end if button=="OK" then whatsign=reslt.impsign end noshift=reslt.noshift defect=reslt.defect keeptxt=reslt.keeptext deline=reslt.deline keeptags=reslt.keeptags addtags=reslt.addtags -- nuke lines for the other signs for x=#slines,1,-1 do efct=slines[x]:match("%a+: %d+,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,([^,]-),.*") if efct~=whatsign then table.remove(slines,x) end end end

-- check start time of the first line (for overall shifting) starttime=slines[1]:match("%a+: %d+,([^,]+)") shiftime=string2time(starttime) if res.mega:match("sign") and noshift then shiftime=0 end

-- importing lines from whatever .ass for x=#slines,1,-1 do local ltype,layer,s_time,e_time,style,actor,margl,margr,margv,eff,txt=slines[x]:match("(%a+): (%d+),([^,]- ),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),(.*)") l2=aline if ltype=="Comment" then l2.comment=true else l2.comment=false end l2.layer=layer -- timing/shifting depending on settings if res.mega:match("import sign") and reslt.matchtime then l2.start_time=basetime l2.end_time=basend else s_time=string2time(s_time) e_time=string2time(e_time) if not noshift then s_time=s_time+basetime e_time=e_time+basetime end l2.start_time=s_time-shiftime l2.end_time=e_time-shiftime end l2.style=style l2.actor=actor l2.margin_l=margl l2.margin_r=margr l2.margin_t=margv l2.effect=eff if defect then l2.effect="" end l2.text=txt atext=txt if keeptxt and actor~="x" then btext2=btext:gsub("{\\[^}]-}","") l2.text=l2.text:gsub("^({\\[^}]-}).*","%1"..btext2) atext=btext2 end if keeptags and actor~="x" then l2.text=addtag(atags,l2.text) l2.text=l2.text:gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) :gsub("({%*?\\[^}]-})",function(tg) return extrakill(tg,2) end) end if addtags and actor~="x" then l2.text="{"..atags.."}"..l2.text l2.text=l2.text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) :gsub("({%*?\\[^}]-})",function(tg) return extrakill(tg,2) end) end

--aegisub.log("\n btext "..btext) subs.insert(act+1,l2) end -- delete line if not keeping if deline then res.keep=false end if not res.keep then subs.delete(act) else -- keep line, restore initial state + comment out atext=btext aline.comment=true aline.start_time=basetime aline.end_time=basend aline.style=basestyle aline.actor="" aline.effect="" aline.layer=baselayer aline.text=atext subs[act]=aline end end

-- EXPORT -- if res.mega=="export sign" then exportsign="" for z,i in ipairs(sel) do line=subs[i] text=line.text if z==1 then snam=line.effect end exportsign=exportsign..line.raw.."\n" end press,reslt=ADD({ {x=0,y=0,class="label",label="Target:",}, {x=0,y=1,class="label",label="Name:",}, {x=1,y=0,width=2,class="dropdown",name="addsign", items={"Add to signs.ass","Save to new file:"},value="Add to signs.ass"}, {x=1,y=1,width=2,class="edit",name="newsign",value=snam}, },{"OK","Cancel"},{ok='OK',close='Cancel'}) if press=="Cancel" then ak() end if press=="OK" then if reslt.newsign=="" then t_error("No name supplied.",true) end newsgn=reslt.newsign:gsub("%.ass$","") if reslt.addsign=="Add to signs.ass" then file=io.open(path.."signs.ass") if not file then file=io.open(path.."signs.ass","w") end sign=file:read("*all") or "" file:close() exportsign=exportsign:gsub("(%u%a+: %d+,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-,[^,]-),[^,]-,(.- )\n","%1,"..reslt.newsign..",%2\n") sign=sign:gsub("%u%a+: [^\n]+,"..esc(reslt.newsign)..",.-\n","") :gsub("^\n*","") sign=sign.."\n"..exportsign file=io.open(path.."signs.ass","w") file:write(sign) end if reslt.addsign=="Save to new file:" then file=io.open(path..newsgn..".ass","w") file:write(exportsign) end file:close() end end

-- IMPORT CHAPTERS if res.mega=="import chptrs" then xml=aegisub.dialog.open("Chapters file (xml)","",scriptpath.."\\","*.xml",false,true) if xml==nil then ak() end file=io.open(xml) xmlc=file:read("*all") io.close(file) chc=0 for ch in xmlc:gmatch("(.-)") do chnam=ch:match("(.-)") chtim=ch:match("(.-)") chtim=chtim:gsub("(%d%d):(%d%d):(%d%d)%.(%d%d%d?)(.*)",function(a,b,c,d,e) if d:len()==2 then d=d.."0" end return d+c*1000+b*60000+a*3600000 end) l2=aline if fr2ms(1)==nil then chs=chtim else chs=fr2ms(ms2fr(chtim)) end l2.start_time=chs l2.end_time=chs+1 l2.actor="chptr" l2.text="{"..chnam.."}" subs.insert(act+chc,l2) chc=chc+1 end end

-- Update Lyrics if res.mega=="update lyrics" then sup1=esc(sub1) sup2=esc(sub2) for z,i in ipairs(sel) do progress("Updating Lyrics... "..round(z/#sel)*100 .."%") line=subs[i] text=line.text

songlyr=sdata if line.style:match(rest) then stylecheck=1 else stylecheck=0 end if res.restr and stylecheck==0 then pass=0 else pass=1 end if res.field=="actor" then marker=line.actor elseif res.field=="effect" then marker=line.effect end denumber=marker:gsub("%d","") -- marked lines if marker:match(sup1.."%d+"..sup2) and denumber==sub1..sub2 and pass==1 then index=tonumber(marker:match(sup1.."(%d+)"..sup2)) puretext=text:gsub("{%*?\\[^}]-}","") lastag=text:match("({\\[^}]-}).$") if songlyr[index]~=nil and songlyr[index]~=puretext then text=text:gsub("^({\\[^}]-}).*","%1"..songlyr[index]) if not text:match("^{\\[^}]-}") then text=songlyr[index] end end songcheck=1 if songlyr[index]~=puretext then if lastag~=nil then text=text:gsub("(.)$",lastag.."%1") end change=" (Changed)" else change="" end aegisub.log("\nupdate: "..puretext.." --> "..songlyr[index]..change) end line.text=text subs[i]=line end end

if res.mega=="update lyrics" and songcheck==0 then press,reslt=ADD({{x=0,y=0,width=1,height=1,class="label",label="The "..res.field.." field of selected lines doesn't match given pattern \""..sub1.."#"..sub2.."\".\n(Or style pattern wasn't matched if restriction enabled.)\n#=number sequence"}},{"ok"},{cancel='ok'}) end

noshift=nil defect=nil keeptxt=nil deline=nil keeptags=nil addtags=nil end -- NUMBERS ------function numbers(subs,sel) zl=zer:len() if sub3:match("[,/;]") then startn,int=sub3:match("(%d+)[,/;](%d+)") else startn=sub3:gsub("%[.-%]","") int=1 end if sub3:match("%[") then numcycle=tonumber(sub3:match("%[(%d+)%]")) else numcycle=0 end if sub3=="" then startn=1 end startn=tonumber(startn) if startn==nil or numcycle>0 and startn>numcycle then t_error("Wrong parameters.",true) end

for z=1,#sel do i=sel[z] line=subs[i] text=line.text

if res.modzero=="number lines" then progress("Numbering... "..round(z/#sel)*100 .."%") index=z count=math.ceil(index/int)+(startn-1) if numcycle>0 and count>numcycle then repeat count=count-(numcycle-startn+1) until count<=numcycle end count=tostring(count) if zl>count:len() then repeat count="0"..count until zl==count:len() end number=sub1..count..sub2

if res.field=="actor" then line.actor=number end if res.field=="effect" then line.effect=number end if res.field=="layer" then line.layer=count end end

if res.modzero=="add to marker" then progress("Adding... "..round(z/#sel)*100 .."%") if res.field=="actor" then line.actor=sub1..line.actor..sub2 elseif res.field=="effect" then line.effect=sub1..line.effect..sub2 elseif res.field=="text" then text=sub1..text..sub2 end end

line.text=text subs[i]=line end end

-- CHAPTERS ------function chopters(subs,sel) if res.marker=="effect" and res.nam=="effect" then t_error("Error. Both marker and name cannot be 'effect'.",true) end if res.chmark then if res.lang~="" then kap=res.lang else kap=res.chap end for z,i in ipairs(sel) do line=subs[i] text=line.text if res.marker=="actor" then line.actor="chptr" end if res.marker=="effect" then line.effect="chptr" end if res.marker=="comment" then text=text.."{chptr}" end if res.nam=="effect" then line.effect=kap end if res.nam=="comment" then text="{"..kap.."}"..text end line.text=text subs[i]=line end else euid=2013 chptrs={} subchptrs={} if res.lang=="" then clang="eng" else clang=res.lang end for i=1,#subs do if subs[i].class=="info" then if subs[i].key=="Video File" then videoname=subs[i].value videoname=videoname:gsub("%.mkv","") end end

if subs[i].class=="dialogue" then line=subs[i] text=line.text actor=line.actor effect=line.effect start=line.start_time if text:match("{[Cc]hapter}") or text:match("{[Cc]hptr}") or text:match("{[Cc]hap}") then comment="chapter" else comment="" end if res.marker=="actor" then marker=actor:lower() end if res.marker=="effect" then marker=effect:lower() end if res.marker=="comment" then marker=comment:lower() end

if marker=="chapter" or marker=="chptr" or marker=="chap" then if res.nam=="comment" then name=text:match("^{([^}]*)}") name=name:gsub(" [Ff]irst [Ff]rame","") name=name:gsub(" [Ss]tart","") name=name:gsub("part a","Part A") name=name:gsub("part b","Part B") name=name:gsub("preview","Preview") else name=effect end

if name:match("::") then main,subname=name:match("(.+)::(.+)") sub=1 else sub=0 end

lineid=start+2013

timecode=math.floor(start/1000) tc1=math.floor(timecode/60) tc2=timecode%60 tc3=start%1000 tc4="00" if tc2==60 then tc2=0 tc1=tc1+1 end if tc1>119 then tc1=tc1-120 tc4="02" end if tc1>59 then tc1=tc1-60 tc4="01" end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end if tc3<100 then tc3="0"..tc3 end linetime=tc4..":"..tc1..":"..tc2.."."..tc3 if linetime=="00:00:00.00" then linetime="00:00:00.033" end

if sub==0 then cur_chptr={id=lineid,name=name,tim=linetime} table.insert(chptrs,cur_chptr) else cur_chptr={id=lineid,subname=subname,tim=linetime,main=main} table.insert(subchptrs,cur_chptr) end

end if line.style=="Default" then euid=euid+text:len() end end end

-- subchapters subchapters={} for c=1,#subchptrs do local ch=subchptrs[c]

ch_main=ch.main ch_uid=ch.id ch_name=ch.subname ch_time=ch.tim

schapter=" \n \n "..ch_name.."\n "..clang.."\n \n "..ch_uid.."\n "..ch_time.."\n 0\n 1\n \n"

subchapter={main=ch_main,chap=schapter} table.insert(subchapters,subchapter) end

-- chapters insert_chapters=""

if res.intro then insert_chapters=" \n "..#subs.."\n 0\n 1\n \n Intro\n "..clang.."\n \n 00:00:00.033\n \n"

end

table.sort(chptrs,function(a,b) return a.tim

for c=1,#chptrs do local ch=chptrs[c]

ch_uid=ch.id ch_name=ch.name ch_time=ch.tim

local subchaps="" for c=1,#subchapters do local subc=subchapters[c] if subc.main==ch_name then subchaps=subchaps..subc.chap end end

chapter=" \n "..ch_uid.."\n 0\n 1\n \n "..ch_name.."\n "..clang.."\n \n"..subchaps.." "..ch_time.."\n \n"

insert_chapters=insert_chapters..chapter end

chapters="\n\n\n \n 0\n 0\n "..euid.."\n"..insert_chapters.." \n"

chdialog= {{x=0,y=0,width=35,class="label",label="Text to export:"}, {x=0,y=1,width=35,height=20,class="textbox",name="copytext",value=chapters}, {x=0,y=21,width=35,class="label",label="File will be saved in the same folder as the .ass file."},}

pressed,reslt=ADD(chdialog,{"Save xml file","mp4-compatible chapters","Cancel","Copy to clipboard"},{cancel='Cancel'}) if pressed=="Copy to clipboard" then clipboard.set(chapters) end scriptpath=ADP("?script") scriptname=aegisub.file_name() scriptname=scriptname:gsub("%.ass","") if ch_script_path=="relative" then path=scriptpath.."\\"..relative_path end if ch_script_path=="absolute" then path=absolute_path end if res.sav=="script" then filename=scriptname else filename=videoname end

if pressed=="Save xml file" then local file=io.open(path.."\\"..filename..".xml","w") file:write(chapters) file:close() end if pressed=="mp4-compatible chapters" then mp4chap="" m4c=1 for ch in chapters:gmatch("(.-)") do chnam=ch:match("(.-)") chtim=ch:match("(.-)") num=tostring(m4c) if num:len()==1 then num="0"..num end chnum="CHAPTER"..num mp4chap=mp4chap..chnum.."="..chtim.."\n"..chnum.."NAME="..chnam.."\n\n" m4c=m4c+1 end chapters=mp4chap:gsub("\n\n$","") chdialog[2].value=chapters pressed,reslt=ADD(chdialog,{"Save txt file","Cancel","Copy to clipboard"},{cancel='Cancel'}) if pressed=="Copy to clipboard" then clipboard.set(chapters) end if pressed=="Save txt file" then local file=io.open(path.."\\"..filename.."chapters.txt","w") file:write(chapters) file:close() end end end end

-- STUFF ------function stuff(subs,sel,act) repl=0 data={} raw=res.dat.."\n" for dataline in raw:gmatch("(.-)\n") do table.insert(data,dataline) end nsel={} for z,i in ipairs(sel) do table.insert(nsel,i) end

if res.stuff=="make style from act. line" then line=subs[act] text=line.text sr=stylechk(subs,line.style) nontra=text:gsub("\\t%b()","") tags=nontra:match("^{\\[^}]-}") or ""

a1=tags:match("\\1a&H(%x%x)&") or sr.color1:match("&H(%x%x)") a2=tags:match("\\2a&H(%x%x)&") or sr.color2:match("&H(%x%x)") a3=tags:match("\\3a&H(%x%x)&") or sr.color3:match("&H(%x%x)") a4=tags:match("\\4a&H(%x%x)&") or sr.color4:match("&H(%x%x)") color1=tags:match("\\1?c&H(%x%x%x%x%x%x)&") or sr.color1:match("&H%x%x(%x%x%x%x%x%x)&") color2=tags:match("\\2c&H(%x%x%x%x%x%x)&") or sr.color2:match("&H%x%x(%x%x%x%x%x%x)&") color3=tags:match("\\3c&H(%x%x%x%x%x%x)&") or sr.color3:match("&H%x%x(%x%x%x%x%x%x)&") color4=tags:match("\\4c&H(%x%x%x%x%x%x)&") or sr.color4:match("&H%x%x(%x%x%x%x%x%x)&") sr.color1="&H"..a1..color1.."&" sr.color2="&H"..a2..color2.."&" sr.color3="&H"..a3..color3.."&" sr.color4="&H"..a4..color4.."&" sr.bold=tags:match("\\b([01])") or sr.bold sr.italic=tags:match("\\i([01])") or sr.italic sr.underline=tags:match("\\u([01])") or sr.underline sr.strikeout=tags:match("\\s([01])") or sr.strikeout sr.fontname=tags:match("\\fn([^\\}]+)") or sr.fontname sr.fontsize=tags:match("\\fs(%d+)") or sr.fontsize sr.scale_x=tags:match("\\fscx([^\\}]+)") or sr.scale_x sr.scale_y=tags:match("\\fscx([^\\}]+)") or sr.scale_y sr.spacing=tags:match("\\fsp([^\\}]+)") or sr.spacing sr.angle=tags:match("\\frz([^\\}]+)") or sr.angle sr.outline=tags:match("\\bord([^\\}]+)") or sr.outline sr.shadow=tags:match("\\shad([^\\}]+)") or sr.shadow sr.align=tags:match("\\an(%d)") or sr.align sr.margin_l=line.margin_l or sr.margin_l sr.margin_r=line.margin_r or sr.margin_r sr.margin_t=line.margin_t or sr.margin_t

stylename={{class="label",label="Style Name"},{y=1,class="edit",name="snam",value=""}, {y=2,class="checkbox",name="switch",label="switch to new style",value=true}, {y=3,class="checkbox",name="del",label="delete style tags from line",value=true}, } pres,rez=ADD(stylename,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end sr.name=rez.snam:gsub(",",";") if rez.del then tags=text:match(STAG) tags=tags :gsub("\\t(%b())",function(t) return "\\tra"..t:gsub("\\","/") end) :gsub("\\%d?[ac]%b&&","") :gsub("\\f[ns][^\\}]+","") :gsub("\\frz[^\\}]+","") :gsub("\\[ibus][01]","") :gsub("\\...d[^\\}]+","") :gsub("\\an%d","") :gsub("\\tra(%b())",function(t) return "\\t"..t:gsub("/","\\") end) :gsub("{}","") line.text=text:gsub(STAG,tags) line.margin_l=0 line.margin_r=0 line.margin_t=0 end if rez.switch then line.style=sr.name end subs[act]=line for i=1,#subs do if subs[i].class=="style" then st=subs[i] if st.name==sr.name then t_error("Style with that name already exists",1) end end if subs[i].class=="dialogue" then subs.insert(i,sr) for z,s in ipairs(sel) do sel[z]=sel[z]+1 end break end end end

-- DATES GUI -- if res.stuff=="format dates" then dategui= {{x=0,y=0,class="dropdown",name="date",value="January 1st",items={"January 1","January 1st","1st of January","1st January"}}, {x=1,y=0,class="checkbox",name="log",label="log",value=false,}} pres,rez=ADD(dategui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end datelog="" end

-- MOTION BLUR GUI if res.stuff=="motion blur" then mblurgui={ {x=0,y=0,width=2,class="checkbox",name="keepblur",label="Keep current blur...",value=true}, {x=0,y=1,class="label",label="...or use blur:"}, {x=1,y=1,class="floatedit",name="mblur",value=mblur or 3}, {x=0,y=2,class="label",label="Distance:"}, {x=1,y=2,class="floatedit",name="mbdist",value=mbdist or 6}, {x=0,y=3,class="label",label="Alpha: "}, {x=1,y=3,class="dropdown",name="mbalfa",value=mbalfa or "80",items={"00","20","40","60","80","A0","C0","D0"}}, {x=0,y=4,width=2,class="checkbox",name="mb3",label="Use 3 lines instead of 2",value=mb3}, {x=0,y=5,width=2,class="label",label="Direction = first 2 points of a clip"}, } pres,rez=ADD(mblurgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end mblur=rez.mblur mbdist=rez.mbdist mbalfa=rez.mbalfa mb3=rez.mb3 end

-- EXPLODE GUI -- if res.stuff=="explode" then -- remember values if exploded then exx=expl_dist_x exy=expl_dist_y htype=ex_hor vtype=ex_ver expropx=xprop expropy=yprop exf=exfad cfad=cfade exinvx=xinv exinvy=yinv implo=implode exquence=exseq exkom=excom seqinv=invseq seqpercent=seqpc rmmbr=exremember else exx=0 exy=0 htype="all" vtype="all" expropx=false expropy=false exf=0 cfad=0 exinvx=false exinvy=false implo=false exquence=false exkom=false seqinv=false seqpercent=100 rmmbr=false end explodegui={ {x=0,y=0,class="label",label="Horizontal distance: "}, {x=1,y=0,class="floatedit",name="edistx",value=exx,hint="Maximum horizontal distance for move"}, {x=0,y=1,class="label",label="Vertical distance: "}, {x=1,y=1,class="floatedit",name="edisty",value=exy,hint="Maximum vertical distance for move"},

{x=2,y=0,class="label",label="direction: "}, {x=3,y=0,class="dropdown",name="hortype",value=htype,items={"only left","mostly left","all","mostly right","only right"}}, {x=4,y=0,class="checkbox",name="xprop",label="proportional",value=expropx,hint="Uniform move rather than random"}, {x=5,y=0,class="checkbox",name="xinv",label="inverse",value=exinvx,hint="Uniform move in the other direction"},

{x=2,y=1,class="label",label="direction: "}, {x=3,y=1,class="dropdown",name="vertype",value=vtype,items={"only up","mostly up","all","mostly down","only down"}}, {x=4,y=1,class="checkbox",name="yprop",label="proportional",value=expropy,hint="Uniform move rather than random"}, {x=5,y=1,class="checkbox",name="yinv",label="inverse",value=exinvy,hint="Uniform move in the other direction"},

{x=0,y=2,class="checkbox",name="ecfo",label="Custom fade:",hint="Default is line length",value=cfad}, {x=1,y=2,class="floatedit",name="exfad",value=exf},

{x=2,y=2,class="checkbox",name="exseq",label="sequence",value=exquence,hint="move in a sequence instead of all at the same time"}, {x=3,y=2,class="floatedit",name="seqpc",value=seqpercent,step=nil,min=1,max=100,hint="how much time should the sequence take up"}, {x=4,y=2,class="label",label="% of move"}, {x=5,y=2,class="checkbox",name="invseq",label="inverse",value=seqinv,hint="inverse sequence"},

{x=0,y=3,class="checkbox",name="impl",label="Implode",value=implo}, {x=1,y=3,class="checkbox",name="rem",label="Same for all lines",value=rmmbr,hint="use only for layered lines with the same text"}, {x=3,y=3,class="checkbox",name="excom",label="Leave original line commented out",value=exkom,width=3}, } pres,rez=ADD(explodegui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end expl_dist_x=rez.edistx expl_dist_y=rez.edisty exfad=rez.exfad cfade=rez.ecfo ex_hor=rez.hortype ex_ver=rez.vertype xprop=rez.xprop yprop=rez.yprop xinv=rez.xinv yinv=rez.yinv implode=rez.impl exseq=rez.exseq seqpc=round(rez.seqpc) invseq=rez.invseq exremember=rez.rem excom=rez.excom exploded=true end

-- Randomized Transforms GUI -- if res.stuff=="randomized transforms" then rine=subs[sel[1]] durone=rine.end_time-rine.start_time rtgui={ {x=0,y=0,class="checkbox",name="rtfad",label="Random Fade",width=2,value=true}, {x=0,y=1,class="checkbox",name="rtdur",label="Random Duration",width=2,value=true}, {x=2,y=0,class="label",label="Min: "}, {x=2,y=1,class="label",label="Max: "}, {x=3,y=0,class="floatedit",name="minfad",width=1,value=math.floor(durone/5),min=0}, {x=3,y=1,class="floatedit",name="maxfad",width=1,value=durone}, {x=4,y=0,class="checkbox",name="rtin",label="Fade In",width=3,value=false,hint="In instead of Out"}, {x=4,y=1,class="checkbox",name="maxisdur",label="Max = Current Duration",width=4,value=true,hint="The maximum will be the duration of each selected line"},

{x=0,y=3,class="label",label="Transform"}, {x=1,y=3,class="dropdown",name="rttag",value="blur", items={"blur","bord","shad","fs","fsp","fscx","fscy","fax","fay","frz","frx","fry","xbord","ybord","xshad","yshad"}}, {x=2,y=3,class="label",label="Min: "}, {x=4,y=3,class="label",label=" Max: "}, {x=3,y=3,class="floatedit",name="mintfn",width=1,value=0,hint="Minimum value for a given tag"}, {x=5,y=3,class="floatedit",name="maxtfn",width=3,value=0,hint="Maximum value for a given tag"},

{x=0,y=4,class="label",width=2,label="Colour Transform"}, {x=2,y=4,class="label",label="Max: "}, {x=3,y=4,class="floatedit",name="rtmaxc",value=100,min=1,max=100,hint="Maximum % of colour change.\nColour tag must be present. Otherwise set to 100%."}, {x=4,y=4,class="label",label="%"}, {x=5,y=4,class="checkbox",name="rtc1",label="\\c ",value=true}, {x=6,y=4,class="checkbox",name="rtc3",label="\\3c ",value=false}, {x=7,y=4,class="checkbox",name="rtc4",label="\\4c ",value=false},

{x=0,y=5,class="checkbox",name="rtacc",label="Use Acceleration",width=2,value=false}, {x=2,y=5,class="label",label="Min: "}, {x=3,y=5,class="floatedit",name="minacc",width=1,value=1,min=0}, {x=4,y=5,class="label",label=" Max:"}, {x=5,y=5,class="floatedit",name="maxacc",width=3,value=1,min=0},

{x=0,y=6,class="checkbox",name="rtmx",label="Random Move X",width=2,value=false}, {x=2,y=6,class="label",label="Min: "}, {x=3,y=6,class="floatedit",name="minmx",width=1,value=0}, {x=4,y=6,class="label",label=" Max:"}, {x=5,y=6,class="floatedit",name="maxmx",width=3,value=0},

{x=0,y=7,class="checkbox",name="rtmy",label="Random Move Y",width=2,value=false}, {x=2,y=7,class="label",label="Min: "}, {x=3,y=7,class="floatedit",name="minmy",width=1,value=0}, {x=4,y=7,class="label",label=" Max:"}, {x=5,y=7,class="floatedit",name="maxmy",width=3,value=0}, } if rtremember then for key,val in ipairs(rtgui) do if val.class~="label" then val.value=rez[val.name] end end end rtchoice={"Fade/Duration","Number Transform","Colour Transform","Help","Cancel"} rthlp={"Fade/Duration","Number Transform","Colour Transform","Cancel"} pres,rez=ADD(rtgui,rtchoice,{ok='Fade/Duration',close='Cancel'}) if pres=="Help" then rthelp={x=0,y=8,width=8,height=4,class="textbox",value="This is supposed to be used after 'split into letters' or with gradients.\n\nFade/Duration Example: Min: 500, Max: 2000.\nA random number between those is generated for each line, let's say 850.\nThis line's duration will be 850ms, and it will have a 850ms fade out.\n\nNumber Transform Example: Blur, Min: 0.6, Max: 2.5\nRandom number generated: 1.7. Line will have: \\t(\\blur1.7)\n\nRandom Colour Transform creates transforms to random colours. \nMax % transform limits how much the colour can change.\n\nAccel works with either transform function.\n\nRandom Move works as an additional option with any function.\nIt can be used on its own if you uncheck other stuff. Works with Fade In."} table.insert(rtgui,rthelp) pres,rez=ADD(rtgui,rthlp,{ok='Fade/Duration',close='Cancel'}) end if pres=="Cancel" then ak() end if pres=="Fade/Duration" then RTM="FD" end if pres=="Number Transform" then RTM="NT" end if pres=="Colour Transform" then RTM="CT" end rtremember=true RTF=rez.rtfad RTD=rez.rtdur MnF=rez.minfad MxF=rez.maxfad RTin=rez.rtin RTMax=rez.maxisdur RTT=rez.rttag MnT=rez.mintfn MxT=rez.maxtfn RTA=rez.rtacc MnA=rez.minacc MxA=rez.maxacc MnX=rez.minmx MxX=rez.maxmx MnY=rez.minmy MxY=rez.maxmy MxC=round(rez.rtmaxc*255/100) rtcol={} if rez.rtc1 then table.insert(rtcol,1) end if rez.rtc3 then table.insert(rtcol,3) end if rez.rtc4 then table.insert(rtcol,4) end end

-- Clone Clip GUI -- if res.stuff=="clone clip" then if clone_h then cchc=clone_h else cchc=2 end if clone_v then ccvc=clone_v else ccvc=2 end if dist_h then cchd=dist_h else cchd=20 end if dist_v then ccvd=dist_v else ccvd=20 end if ccshift then ccsh=ccshift else ccsh=0 end ccgui={ {x=0,y=0,class="label",label="Horizontal distance: "}, {x=1,y=0,class="intedit",name="hdist",value=cchd,min=1}, {x=0,y=1,class="label",label="Horizontal clones: "}, {x=1,y=1,class="intedit",name="hclone",value=cchc,min=1}, {x=0,y=2,class="label",label="Vertical distance: "}, {x=1,y=2,class="intedit",name="vdist",value=ccvd,min=1}, {x=0,y=3,class="label",label="Vertical clones: "}, {x=1,y=3,class="intedit",name="vclone",value=ccvc,min=1}, {x=0,y=4,class="label",label="Shift even rows by:"}, {x=1,y=4,class="intedit",name="ccshift",value=ccsh,min=0}, } pres,rez=ADD(ccgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pres=="Cancel" then ak() end clone_h=rez.hclone dist_h=rez.hdist clone_v=rez.vclone dist_v=rez.vdist ccshift=rez.ccshift end

-- DISSOLVE GUI ------if res.stuff=="dissolve text" then if dlast then ddistance=v_dist ddlines=dlines dshape=shape dalter=alternate dissin=disin otherd=otherdis v2direction=v2d else ddistance=10 ddlines=10 dshape="square" dalter=true dissin=false otherd=false v2direction="randomly" end dissgui={ {x=0,y=0,class="label",label="Distance between points: "}, {x=1,y=0,class="floatedit",name="ddist",value=ddistance,min=4,step=2}, {x=0,y=1,class="label",label="Shape of clips:"}, {x=1,y=1,class="dropdown",name="shape",items={"square","square 2","diamond","triangle 1","triangle 2","hexagon","wave/hexagram","vertical lines","horizontal lines"},value=dshape}, {x=0,y=2,class="checkbox",name="alt",label="Shift even rows (all except vertical/horizontal lines)",value=dalter,width=2}, {x=0,y=3,class="checkbox",name="disin",label="Reverse effect (fade in rather than out)",value=dissin,width=2}, {x=0,y=4,class="checkbox",name="otherdiss",label="Dissolve v2. ... Lines:",value=otherd,hint="only square, diamond, vertical lines"}, {x=1,y=4,class="floatedit",name="modlines",value=ddlines,min=6,step=2}, {x=0,y=5,class="label",label=" Dissolve v2: Dissolve"}, {x=1,y=5,class="dropdown",name="v2dir",items={"randomly","from top","from bottom","from left","from right"},value=v2direction}, } pres,rez=ADD(dissgui,{"OK","What Is This","Cancel"},{ok='OK',close='Cancel'}) if pres=="What Is This" then dishelp={x=0,y=6,width=10,height=8,class="textbox",value="The script can either automatically draw a clip around the text,\nor you can make your own clip.\nThe automation only considers position, alignment, and scaling,\nso for anything more complex, make your own.\nYou can just try it without a clip,\nand if the result isn't right, draw a clip first. (Only 4 points!)\n\n'Distance between points' will be the distance between the\nstarting points of all the little iclips.\nLess Distance = more clips = more lag,\nso use the lowest values only for smaller text.\nYou can run this on one line or fbf lines.\nThe ideal 'fade' is as many frames as the given Distance.\nThat way the clips grow by 1 pixel per frame.\nAny other way doesn't look too good,\nbut you can apply Distance 10 over 20 lines\nand have each 2 consecutive lines identical.\nMore Distance than lines doesn't look so bad, and the effect is 'faster'.\nIf you apply this to 1 line, the line will be split to have the effect applied to as many frames as the Distance is. (This is preferred.)\nFor hexagon, the actual distance is twice the input. (It grows faster.)\n\nThe shapes should be self-explanatory, so just experiment.\n\n'Shift even rows' means that even rows will have an offset\nfrom odd rows by half of the given Distance.\nNot checking this will have a slightly different and less regular effect,\nthough it also depends on the shape. Again, experiment.\n\nIf you need to apply this to several layers, you have to do it one by one. The GUI remembers last values. But more layers = more lag.\n\nAll kinds of things can make this lag, so use carefully.\nLines are less laggy than other shapes.\nHorizontal lines are the least laggy. (Unless you have vertical text.)\n\nFor longer fades, use more Distance.\nThis works great with vertical lines but is pretty useless with horizontal.\n\n'Reverse effect' is like fade in while the default is fade out.\nWith one line selected, it applies to the first frames.\n\n'Dissolve v2' is a different kind of dissolve\nand only works with square, diamond, and vertical lines.\nLine count for this is independent on distance between points.\nIt's the only effect that allows Distance 4.\n'Shift even rows' has no effect here.\n\nYou can set a direction of Dissolve v2.\nObviously top and bottom is nonsense for vertical lines.\n'Reverse effect' reverses the direction too, so choose the opposite.\n\nThere may be weird results with some combinations of settings.\nThere may be some malfunctions, as the script is pretty complex.\nSome of them -might- be fixed by reloading automation scripts.\nMakes no sense with \\move. Nukes \\fad.\n\nThere are some fun side effects.\nFor example with 'square 2' and 'Shift even rows',\nyou get a brick wall on the last frame."} table.insert(dissgui,dishelp) pres,rez=ADD(dissgui,{"OK","Cancel"},{ok='OK',close='Cancel'}) end if pres=="Cancel" then ak() end dlast=true v_dist=rez.ddist shape=rez.shape alternate=rez.alt disin=rez.disin otherdis=rez.otherdiss dlines=rez.modlines v2d=rez.v2dir dis2=false if v2d=="randomly" then dir=5 end if v2d=="from top" then dir=8 end if v2d=="from bottom" then dir=2 end if v2d=="from left" then dir=4 end if v2d=="from right" then dir=6 end if not otherdis and v_dist==4 then t_error("Distance 4 is only allowed for square mod. Changing to 6.") v_dist=6 end if otherdis then if shape=="square" or shape=="diamond" or shape=="vertical lines" then dis2=true else dis2=false end if shape=="square" then alternate=false end if shape=="diamond" then alternate=true end end if dis2 and #sel==1 then linez=dlines elseif dis2 and #sel>1 then linez=#sel else linez=v_dist end

-- DISSOLVE create lines if only one selected ------if #sel==1 then rine=subs[sel[1]] rine.text=rine.text:gsub("\\fad%(.-%)","") start=rine.start_time endt=rine.end_time startf=ms2fr(start) endf=ms2fr(endt) lframes=ms2fr(endt-start) if lframesb end) end

-- DISSOLVE Initial Calculations ------line=subs[sel[1]] text=line.text text=text:gsub("\\clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) a=math.floor(a) b=math.floor(b) c=math.ceil(c) d=math.ceil(d) return string.format("\\clip(m %d %d l %d %d %d %d %d %d)",a,b,c,b,c,d,a,d) end) -- draw clip when no clip present if not text:match("\\clip") then styleref=stylechk(subs,line.style) vis=text:gsub("%b{}","") width,height,descent,ext_lead=aegisub.text_extents(styleref,vis) bord=text:match("\\bord([%d%.]+)") if bord==nil then bord=styleref.outline end bord=math.ceil(bord) scx=text:match("\\fscx([%d%.]+)") if scx==nil then scx=styleref.scale_x end scx=scx/100 scy=text:match("\\fscy([%d%.]+)") if scy==nil then scy=styleref.scale_y end scy=scy/100 wi=round(width) he=round(height) text2=getpos(subs,text) if not text:match("\\pos") then text=text2 end xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if h_al=="left" then cx1=xx cx2=xx+wi*scx end if h_al=="right" then cx1=xx-wi*scx cx2=xx end if h_al=="mid" then cx1=xx-wi/2*scx cx2=xx+wi/2*scx end if v_al=="top" then cy1=yy cy2=yy+he*scy end if v_al=="bottom" then cy1=yy-he*scy cy2=yy end if v_al=="mid" then cy1=yy-he/2*scy cy2=yy+he/2*scy end cx1=math.floor(cx1-bord) cx2=math.ceil(cx2+bord) cy1=math.floor(cy1-bord) cy2=math.ceil(cy2+bord) text=addtag("\\clip(m "..cx1.." "..cy1.." l "..cx2.." "..cy1.." "..cx2.." "..cy2.." "..cx1.." "..cy2..")",text) end -- get outermost clip points even if it's irregular (though it should be fucking regular) exes={} wais={} klip=text:match("\\clip%(m [%d%-]+ [%d%-]+ l [%d%-]+ [%d%-]+ [%d%-]+ [%d%-]+ [%d%-]+ [%d%-]+") for ex,wai in klip:gmatch("([%d%-]+) ([%d%-]+)") do table.insert(exes,tonumber(ex)) table.insert(wais,tonumber(wai)) end table.sort(exes) table.sort(wais) x1=exes[1]-2 x2=exes[4]+2 y1=wais[1]-2 y2=wais[4]+2 width=x2-x1 height=y2-y1 h_dist=2*v_dist if shape=="hexagon" then h_dist=math.floor(h_dist*2) end rows=math.ceil(height/v_dist) rows2=math.ceil(height/v_dist/2) points=math.ceil(width/h_dist)+1 if shape=="horizontal lines" then vert=2*v_dist rows=math.ceil(rows/2)+1 else vert=v_dist end if shape:match("triangle") or shape=="wave/hexagram" then rows=rows+1 end xpoints={} for w=1,points do point=x1+h_dist*(w-1) table.insert(xpoints,point) end ypoints={} for w=1,rows do point=y1+vert*(w-1) table.insert(ypoints,point) end ypoints2={} for w=1,rows2 do point=y1+2*v_dist*(w-1) table.insert(ypoints2,point) end

-- this is all centers of individual iclip shapes allpoints1={} for w=1,#ypoints do for z=1,#xpoints do u=0 if alternate and w%2==0 then u=h_dist/2 else u=0 end -- every even row is shifted by half of h_dist from odd rows rnum=math.random(2000,6000) if dir==5 then rindex=rnum end if dir==8 then rindex=rnum*ypoints[w]^2 end if dir==4 then rindex=rnum*xpoints[z]^2 end if dir==2 then rindex=0-rnum*ypoints[w]^2 end if dir==6 then rindex=0-rnum*xpoints[z]^2 end point={xpoints[z]+u,ypoints[w],rindex} table.insert(allpoints1,point) end end

allpoints2={} for w=1,#ypoints2 do for z=1,#xpoints do u=0 if alternate and w%2==0 then u=h_dist/2 else u=0 end -- every even row is shifted by half of h_dist from odd rows rnum=math.random(2000,6000) if dir==5 then rindex=rnum end if dir==8 then rindex=rnum*ypoints2[w]^2 end if dir==4 then rindex=rnum*xpoints[z]^2 end if dir==2 then rindex=0-rnum*ypoints2[w]^2 end if dir==6 then rindex=0-rnum*xpoints[z]^2 end point={xpoints[z]+u,ypoints2[w],rindex} table.insert(allpoints2,point) end end

if dis2 and shape=="square" or shape=="square 2" or shape=="hexagon" then allpoints=allpoints2 else allpoints=allpoints1 end if dis2 and shape=="vertical lines" then allpoints={} for w=1,#xpoints do rnum=math.random(2000,6000) if dir==4 then rindex=rnum*xpoints[w]^2 elseif dir==6 then rindex=0-rnum*xpoints[w]^2 else rindex=rnum end table.insert(allpoints,{xpoints[w],0,rindex}) end end if dis2 then table.sort(allpoints,function(a,b) return a[3]

-- DISSOLVE v2 Calculations ------if dis2 then d2c=0 fullclip="" dis2tab={} rnd=1 ppl=#allpoints/linez for w=1,#allpoints do pt=allpoints[w] vd=v_dist

if shape=="square" then krip="m "..pt[1]-vd.." "..pt[2]-vd.." l "..pt[1]+vd.." "..pt[2]-vd.." "..pt[1]+vd.." "..pt[2]+vd.." "..pt[1]-vd.." "..pt[2]+vd.." " end

if shape=="diamond" then krip="m "..pt[1].." "..pt[2]-vd.." l "..pt[1]+vd.." "..pt[2].." "..pt[1].." "..pt[2]+vd.." "..pt[1]-vd.." "..pt[2].." " end

if shape=="vertical lines" then krip="m "..pt[1]-v_dist.." "..y1.." l "..pt[1]+v_dist.." "..y1.." "..pt[1]+v_dist.." "..y2.." "..pt[1]-v_dist.." "..y2.." " end

fullclip=fullclip..krip d2c=d2c+1 if d2c>=math.floor(ppl) and w>=ppl*rnd then d2c=0 rnd=rnd+1 table.insert(dis2tab,fullclip) end end end -- DISSOLVE END ------end

-- Fadeworks GUI -- if res.stuff=="fadeworks" then fadegui={ {x=0,y=0,class="label",label="[FadeWorks]"}, {x=1,y=0,class="label",label="Lines before start time"}, {x=2,y=0,class="label",label="Lines after end time"}, {x=0,y=1,class="label",label="Lines to create:"}, {x=1,y=1,class="intedit",name="LF",value=0,min=0}, {x=2,y=1,class="intedit",name="RF",value=0,min=0}, {x=0,y=2,class="label",label="Frames per line:"}, {x=1,y=2,class="intedit",name="LFram",value=1,min=1}, {x=2,y=2,class="intedit",name="RFram",value=1,min=1}, {x=0,y=3,class="label",label="Shift each line by:"}, {x=1,y=3,class="intedit",name="LFshift",value=1,min=1,hint="Should be same/lower than # of frames"}, {x=2,y=3,class="intedit",name="RFshift",value=1,min=1,hint="Should be same/lower than # of frames"}, {x=0,y=4,class="label",label="X distance:"}, {x=1,y=4,class="floatedit",name="LFX"}, {x=2,y=4,class="floatedit",name="RFX"}, {x=0,y=5,class="label",label="Y distance:"}, {x=1,y=5,class="floatedit",name="LFY"}, {x=2,y=5,class="floatedit",name="RFY"}, {x=0,y=6,class="label",label="X acceleration:"}, {x=1,y=6,class="floatedit",name="LFacx",value=1,min=0}, {x=2,y=6,class="floatedit",name="RFacx",value=1,min=0}, {x=0,y=7,class="label",label="Y acceleration:"}, {x=1,y=7,class="floatedit",name="LFacy",value=1,min=0}, {x=2,y=7,class="floatedit",name="RFacy",value=1,min=0}, {x=0,y=8,class="label",label="fbf transform:"}, {x=1,y=8,class="edit",name="Lfbf",value="\\",hint="tags to transform FROM"}, {x=2,y=8,class="edit",name="Rfbf",value="\\",hint="tags to transform TO"}, {x=0,y=9,class="label",label="fbf alpha tf:"}, {x=1,y=9,class="edit",name="Lalf",hint="start alpha in hexadecimal"}, {x=2,y=9,class="edit",name="Ralf",hint="end alpha in hexadecimal"}, {x=0,y=10,class="label",label="\\t acceleration:"}, {x=1,y=10,class="floatedit",name="LFact",value=1,min=0}, {x=2,y=10,class="floatedit",name="RFact",value=1,min=0},

{x=0,y=11,class="label",label="Flickering:"}, {x=1,y=11,class="checkbox",name="LFad",label="Fade in"}, {x=2,y=11,class="checkbox",name="RFad",label="Fade out"},

{x=0,y=13,class="label",label="Fade mode:"}, {x=1,y=13,width=2,class="checkbox",name="Fade",label="Use existing \\fad for line length and start/end point",hint="experimental feature intended to make overlapping lines"}, {x=0,y=14,class="label",label="add transform:"}, {x=1,y=14,class="edit",name="LFtra",value="\\",hint="tags to transform FROM"}, {x=2,y=14,class="edit",name="RFtra",value="\\",hint="tags to transform TO"},

{x=3,y=3,class="label",label="frames"}, {x=3,y=4,class="label",label="pixels"}, {x=3,y=5,class="label",label="pixels"}, } if resfade then for k,v in ipairs(fadegui) do if v.name then v.value=rez[v.name] end end end pres,rez=ADD(fadegui,{"OK","Cancel"},{ok='OK',close='Cancel'}) resfade=rez if pres=="Cancel" then ak() end if rez.Fade then rez.LFad=false rez.RFad=false end end

-- What is the Matrix -- if res.stuff=="what is the Matrix?" then matrixgui={ {x=0,y=0,class="label",label="Max. transformations per letter: "}, {x=1,y=0,class="intedit",name="tpl",value=4,min=2}, {x=0,y=1,class="label",label="Frames to stay the same: "}, {x=1,y=1,class="intedit",name="fts",value=2,min=1}, {x=0,y=2,class="label",label="Character set: "},

{x=1,y=2,class="dropdown",name="charset",items={"UPPERCASE","lowercase","Both","More","Everything"},value ="Both"}, {x=0,y=3,class="label",label="Chance to keep letter (0-10):"}, {x=1,y=3,class="intedit",name="mkeep",value=5,min=0,max=10}, {x=0,y=4,width=2,class="checkbox",name="showall",label="Show all letters from the start",value=true,}, {x=0,y=5,width=2,class="label",label="Monospace fonts are optimal. For others, use left alignment."}, } if matrixres then for key,val in ipairs(matrixgui) do if val.class=="checkbox" or val.class=="dropdown" or val.class=="intedit" then val.value=rez[val.name] end end end pres,rez=ADD(matrixgui,{"What Is the Matrix?","Cancel"},{ok='What Is the Matrix?',close='Cancel'}) if pres=="Cancel" then ak() end AB="ABCDEFGHIJKLMNOPQRSTUVWXYZ" ab="abcdefghijklmnopqrstuvwxyz" Ab="!?()$&+-=" aB="@#%^*/[]';:,.|" if rez.charset=="UPPERCASE" then chset=AB.." " elseif rez.charset=="lowercase" then chset=ab.." " elseif rez.charset=="Both" then chset=AB..ab.." " elseif rez.charset=="More" then chset=AB..ab..Ab.." " else chset=AB..ab..Ab..aB.." " end ABC=chset:len() mframes=rez.tpl fpl=rez.fts matrixres=rez end

if res.stuff=="time by frames" then frs=res.rep1:match("%-?%d+") or 0 fre=res.rep2:match("%-?%d+") or 0 rine=subs[sel[1]] fstart=ms2fr(rine.start_time) fendt=ms2fr(rine.end_time) rine.start_time=fr2ms(fstart) rine.end_time=fr2ms(fendt) subs[sel[1]]=rine if frs==0 and fre==0 then t_error("Use Left/Right to input \nnumber of frames \nto shift by for start/end.",true) end end

if res.stuff=="reverse fbf times" then tbf={} table.sort(sel,function(a,b) return subs[a].start_time

if res.stuff=="duplicate and shift lines" then FB=tonumber(res.rep1:match("^%d+")) or 0 FA=tonumber(res.rep2:match("^%d+")) or 0 if FA+FB==0 then t_error("Use the Left and Right fields to set how many times you want to duplicate the line.",true) end FALL=FA+FB+1 FB1=FB+1 end

KO1=subs[sel[1]].start_time

if res.stuff:match("replacer") or res.stuff=="fix kara tags for fbf lines" then table.sort(sel,function(a,b) return a>b end) end

-- LINES START HERE ------for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text style=line.style

if res.stuff=="reverse fbf times" then break end

-- What is the Matrix -- if res.stuff=="what is the Matrix?" then start=line.start_time endt=line.end_time startf=ms2fr(start) tags=text:match("^{\\[^}]-}") or "" visible=text:gsub("%b{}","") ltrs={} ltrs2={} matches=re.find(visible,".") for l=1,#matches do table.insert(ltrs,matches[l].str) r=math.random(1,ABC) table.insert(ltrs2,chset:sub(r,r)) end base="" lines={} for l=1,#ltrs do for f=1,mframes do if f1 then letter=lastletter end if l==1 and letter==" " then letter="e" end if l==#ltrs and letter==" " then letter="s" end txt=base..letter lastletter=letter else letter=ltrs[l] txt=base..letter base=txt end if not rez.showall then txt=txt.."{\\alpha&HFF&}" end for n=l+1,#ltrs do if rez.showall then y=math.random(1,ABC) letter=chset:sub(y,y) z=math.random(0,9) if zl then letter=ltrs2[n] end ltrs2[n]=letter if n==#ltrs and letter==" " then letter="k" end txt=txt..letter else txt=txt..ltrs[n] end end txt=tags..txt fact=l*mframes+f-mframes-1 startfr=startf+fact*fpl st=fr2ms(startfr) et=fr2ms(startfr+fpl) l2={txt,st,et} table.insert(lines,l2) end lastletter=nil end for ln=#lines,1,-1 do lin=lines[ln] line.text=lin[1] line.start_time=lin[2] line.end_time=lin[3] if ln==#lines and endt>lin[3] then line.end_time=endt end subs.insert(i+1,line) end line.comment=true end

if res.stuff=="save/load" and z==1 then if savedata==nil then savedata="" end if res.dat~="" then savedata=savedata.."\n\n"..res.dat savedata=savedata :gsub("^\n\n","") :gsub("\n\n\n","\n\n") ADD({{class="label",label="Data saved.",x=0,y=0,width=20,height=2}},{"OK"},{close='OK'}) else

ADD({{x=0,y=0,width=50,height=18,class="textbox",name="savetxt",value=savedata},},{"OK"},{close='OK'}) end end

if res.stuff=="replacer" then lim=sub3:match("^%d+") if lim==nil then limit=1 else limit=tonumber(lim) end replicant1=sub1:gsub("\\","\\"):gsub("\\\\","\\") replicant2=sub2:gsub("\\","\\"):gsub("\\\\","\\") tk=text count=0 if res.regex=="lua patterns" then repeat text=text:gsub(replicant1,replicant2) count=count+1 until count==limit if text~=tk then repl=repl+1 if res.log then r1=replicant1:gsub("%%%(","_L_"):gsub("%%%)","_R_"):gsub("%(",""):gsub("%)",""):gsub("_L_","%%%("):gsub("_ R_","%%%)") for l1 in tk:gmatch(r1) do aegisub.log("\nOrig: "..l1) l2=l1:gsub(replicant1,replicant2) aegisub.log("\nMod: "..l2) end end end else repeat text=re.sub(text,replicant1,replicant2) count=count+1 until count==limit if text~=tk then repl=repl+1 if res.log then for r1 in re.gfind(tk,replicant1) do aegisub.log("\nOrig: "..r1) r2=re.sub(r1,replicant1,replicant2) aegisub.log("\nMod: "..r2) end end end

end end

if res.stuff=="lua calc" then lim=sub3:match("^%d+") if lim==nil then limit=1 else limit=tonumber(lim) end replicant1=sub1:gsub("\\","\\") replicant2=sub2:gsub("\\","\\") replicant1=sub1:gsub("\\\\","\\") replicant2=sub2:gsub("\\\\","\\") replicant2="||"..replicant2.."||" replicant2=replicant2:gsub("%.%.","||") tk=text count=0 repeat text=text:gsub(replicant1,function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) tab1={"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"} tab2={a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p} r2=replicant2 asd=1 repeat for r=1,16 do r2=r2 :gsub(tab1[r].."%*([%d%.]+)",function(num) return tab2[r]*tonumber(num) end) :gsub(tab1[r].."%/([%d%.]+)",function(num) return tab2[r]/tonumber(num) end) :gsub(tab1[r].."%+([%d%.]+)",function(num) return tab2[r]+tonumber(num) end) :gsub(tab1[r].."%-([%d%.]+)",function(num) return tab2[r]-tonumber(num) end) :gsub("([%d%.]+)%*([%d%.]+)",function(n1,n2) return tonumber(n1)*tonumber(n2) end) :gsub("([%d%.]+)%/([%d%.]+)",function(n1,n2) return tonumber(n1)/tonumber(n2) end) :gsub("([%d%.]+)%+([%d%.]+)",function(n1,n2) return tonumber(n1)+tonumber(n2) end) :gsub("([%d%.]+)%-([%d%.]+)",function(n1,n2) return tonumber(n1)-tonumber(n2) end) end for r=1,16 do if tab2[r]~=nil then r2=r2:gsub("([|%*%/%+%-])"..tab1[r].."|","%1"..tab2[r].."|") r2=r2:gsub("%("..tab1[r].."%)","("..tab2[r]..")") end end r2=r2:gsub("round%(([^%)]+)%)",function(num) return math.floor(tonumber(num)+0.5) end) asd=asd+1 until not r2:match("[%*%/%+%-]") or asd==12 r2=r2:gsub("||","") return r2 end) count=count+1 until count==limit if text~=tk then repl=repl+1 end end if res.stuff=="make comments visible" then text=text:gsub("{([^\\}]-)}","%1") end if res.stuff=="switch commented/visible" then text=text :gsub("\\N","_br_") :gsub("{([^\\}]-)}","}%1{") :gsub("^([^{]+)","{%1") :gsub("([^}]+)$","%1}") :gsub("([^}])({\\[^}]-})([^{])","%1}%2{%3") :gsub("^({\\[^}]-})([^{])","%1{%2") :gsub("([^}])({\\[^}]-})$","%1}%2") :gsub("{}","") :gsub("_br_","\\N") end if res.stuff=="reverse text" then tags=text:match(STAG) or "" text=text:gsub("%b{}","") t2="" for L in re.gfind(text,".") do t2=L..t2 end text=tags..t2 end if res.stuff=="reverse words" then tags=text:match(STAG) or "" text=text:gsub("%b{}","") nt="" for l in text:gmatch("%S+") do nt=" "..l..nt end nt=nt:gsub("^ ","") text=tags..nt end

-- MOTION BLUR ------if res.stuff=="motion blur" then if text:match("\\clip%(m") then if not text:match("\\pos") then text=getpos(subs,text) end if not rez.keepblur then text=addtag("\\blur"..mblur,text) end text=text:gsub("{%*?\\[^}]-}",function(tg) return duplikill(tg) end) c1,c2,c3,c4=text:match("\\clip%(m ([%-%d%.]+) ([%-%d%.]+) l ([%-%d%.]+) ([%-%d%.]+)") if c1==nil then t_error("There seems to be something wrong with your clip",true) end text=text:gsub("\\clip%b()","") text=addtag("\\alpha&H"..mbalfa.."&",text) cx=c3-c1 cy=c4-c2 cdist=math.sqrt(cx^2+cy^2) mbratio=cdist/mbdist*2 mbx=round(cx/mbratio,2) mby=round(cy/mbratio,2) text2=text:gsub("\\pos%(([%-%d%.]+),([%-%d%.]+)",function(a,b) return "\\pos("..a-mbx..","..b-mby end) l2=line l2.text=text2 subs.insert(i+1,l2) table.insert(sel,sel[#sel]+1) if rez.mb3 then line.text=text subs.insert(i+1,line) table.insert(sel,sel[#sel]+1) end text=text:gsub("\\pos%(([%-%d%.]+),([%-%d%.]+)",function(a,b) return "\\pos("..a+mbx..","..b+mby end) else noclip=true end end

-- REVERSE TRANSFORMS ------if res.stuff=="reverse transforms" then styleref=stylechk(subs,line.style) text=text:gsub("\\1c","\\c") tags=text:match(STAG) or "" text=text:gsub(STAG,"") tags=cleantr(tags) tags=duplikill(tags) tags=tags:gsub("\\fs(%d)","\\fsize%1") notrans=tags:gsub("\\t%b()","") for tr in tags:gmatch("\\t%b()") do tr=tr:gsub("\\i?clip%([^%)]+%)","") :gsub("\\t%(","") :gsub("%)$","") for tag in tr:gmatch("\\[1234]?%a+") do if not notrans:match(tag) then tags=fill_in(tags,tag) end tags=tags:gsub(tag.."([^\\}]+)([^}]-)"..tag.."([^\\}%)]+)",tag.."%3%2"..tag.."%1") end end tags=tags:gsub("(i?clip%([^%)]+%))([^}]-\\t[^}]-)(i?clip%([^%)]+%))","%3%2%1") :gsub("\\fsize","\\fs") text=tags..text end if res.stuff=="fake capitals" then tags=text:match(STAG) or "" text=text:gsub(STAG,"") text=re.sub(text,"(\\u)","{\\\\fs"..sub1.."}\\1{\\\\fs}") text=text:gsub("{\\fs}(%-?){\\fs"..sub1.."}","%1") text=tags..text text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") end

if res.stuff=="format dates" then text2=text:gsub("%b{}","") if rez.date=="January 1" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)(st|nd|th)","\\1 \\2") text=re.sub(text,"(\\d+)(st|nd|th|st of|nd of|th of) (January|February|March|April|May|June|July|August|September|October|November|December)","\\3 \\1") end if rez.date=="January 1st" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)","\\1 \\2th") text=re.sub(text,"(\\d+)(st|nd|th|st of|nd of|th of) (January|February|March|April|May|June|July|August|September|October|November|December)","\\3 \\1th") text=text:gsub("(%d)thth","%1th") :gsub("1thst","1st") :gsub("2thnd","2nd") :gsub("1th","1st") :gsub("2th","2nd") end if rez.date=="1st of January" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)(st|nd|th)?","\\2\\3 of \\1") text=re.sub(text,"(\\d+) of (January|February|March|April|May|June|July|August|September|October|November|December)","\\1th of \\2") text=text:gsub("(%d)thth","%1th") :gsub("1thst","1st") :gsub("2thnd","2nd") :gsub("1th","1st") :gsub("2th","2nd") text=re.sub(text,"(\\d+)(st|nd|th) (January|February|March|April|May|June|July|August|September|October|November|December)","\\1\\2 of \\3") end if rez.date=="1st January" then text=re.sub(text,"(January|February|March|April|May|June|July|August|September|October|November|December) (\\d+)(st|nd|th)?","\\2\\3 \\1") text=re.sub(text,"(\\d+) (January|February|March|April|May|June|July|August|September|October|November|December)","\\1th \\2") text=text:gsub("(%d)thth","%1th") :gsub("1thst","1st") :gsub("2thnd","2nd") :gsub("1th","1st") :gsub("2th","2nd") text=re.sub(text,"(\\d+)(st|nd|th) of (January|February|March|April|May|June|July|August|September|October|November|December)","\\1\\2 \\3") end textn=text:gsub("%b{}","") if text2~=textn then datelog=text2.." -> "..textn.."\n"..datelog end end

if res.stuff=="transform \\k to \\t\\alpha" then repeat text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until not text:match("{(\\[^}]-)}{(\\[^}]-)}") if text:match("^{[^}]-\\alpha") then alf=text:match("^{[^}]-\\alpha&H(%x%x)&") else alf="00" end text=text:gsub("\\alpha&H%x%x&","") tab={} for kpart in text:gmatch("{[^}]-\\k[fo][%d%.]+[^}]-}[^{]*") do table.insert(tab,kpart) end lastim=0 text="" for k=1,#tab do part=tab[k] tim=tonumber(part:match("\\k[fo]([%d%.]+)"))*10 part=part:gsub("\\k[fo][%d%.]+","\\alpha&HFF&\\t("..lastim..","..lastim+tim..",\\alpha&H"..alf.."&)") tab[k]=part lastim=lastim+tim text=text..tab[k] end end

-- SPLIT and EXPLODE ------if res.stuff=="split into letters" or res.stuff=="explode" then l2=line tags=text:match(STAG) or "" vis=text:gsub("%b{}","") af="{\\alpha&HFF&}" a0="{\\alpha&H00&}" letters={} ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(letters,ltrmatches[l].str) end if savetab==nil then savetab={} end -- create texts for all resulting lines for l=#letters,1,-1 do tx=af ltr=a0..letters[l]..af for t=1,#letters do ltr2=letters[t] if t==l then ltr2=ltr end tx=tx..ltr2 end tx=textmod(text,tx) txt2=tags..tx if not txt2:match("\\pos") then txt2=getpos(subs,txt2) end txt2=txt2:gsub("{\\alpha&HFF&}$","") txt2=txt2:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") txt2=txt2:gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) -- Explode if res.stuff=="explode" then dur=line.end_time-line.start_time if cfade then FO=exfad else FO=dur end if implode then expfad="\\fad("..FO..",0)" else expfad="\\fad(0,"..FO..")" end if FO==0 then EFO="" else EFO=expfad end if ex_hor=="all" then ex1a=0-expl_dist_x ex1b=expl_dist_x end if ex_hor=="only left" then ex1a=0-expl_dist_x ex1b=0 end if ex_hor=="only right" then ex1a=0 ex1b=expl_dist_x end if ex_hor=="mostly left" then ex1a=0-expl_dist_x ex1b=expl_dist_x/3 end if ex_hor=="mostly right" then ex1a=0-expl_dist_x/3 ex1b=expl_dist_x end if ex_ver=="all" then ex2a=0-expl_dist_y ex2b=expl_dist_y end if ex_ver=="only up" then ex2a=0-expl_dist_y ex2b=0 end if ex_ver=="only down" then ex2a=0 ex2b=expl_dist_y end if ex_ver=="mostly up" then ex2a=0-expl_dist_y ex2b=expl_dist_y/3 end if ex_ver=="mostly down" then ex2a=0-expl_dist_y/3 ex2b=expl_dist_y end rvrs=#letters-l+1 if xinv then xind=rvrs else xind=l end if yinv then yind=rvrs else yind=l end if invseq then seqt=rvrs else seqt=l end if implode then seqt=#letters-seqt+1 end if exremember and z<#sel then tab=savetab[rvrs] ex1=tab.x1 ex2=tab.x2 else if xprop then xhdist=(ex1b-ex1a)/#letters ex1=round(ex1a+xhdist*xind) else ex1=math.ceil(math.random(ex1a,ex1b)) end if yprop then xvdist=(ex2b-ex2a)/#letters ex2=round(ex2a+xvdist*yind) else ex2=math.ceil(math.random(ex2a,ex2b)) end end if exremember and z==#sel then table.insert(savetab,{x1=ex1,x2=ex2}) end

-- move sequence if exseq then tfrag=round(dur/#letters/(100/seqpc)) xt1=tfrag*seqt-tfrag else xt1=0 end xt2=dur if implode and exseq then xt2=dur-xt1 xt1=0 end txt2=txt2:gsub("\\move%(([%d%.%-]+),([%d%.%-]+).-%)","\\pos(%1,%2)") txt2=txt2:gsub("\\fad%(.-%)","") if implode then txt2=txt2:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)", function(a,b) return EFO.."\\move("..a+ex1..","..b+ex2..","..a..","..b..","..xt1..","..xt2..")" end) else txt2=txt2:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)%)", function(a,b) return EFO.."\\move("..a..","..b..","..a+ex1..","..b+ex2..","..xt1..","..xt2..")" end) end txt2=txt2:gsub("{\\[^}]-}$","") end l2.text=txt2 if letters[l]~=" " then subs.insert(i+1,l2) table.insert(sel,sel[#sel]+z) end

end line.comment=true end

-- Clone Clip if res.stuff=="clone clip" and text:match("\\clip%((.-)%)") then text=text:gsub("\\clip%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)",function(a,b,c,d) a=math.floor(a) b=math.floor(b) c=math.ceil(c) d=math.ceil(d) return string.format("\\clip(m %d %d l %d %d %d %d %d %d)",a,b,c,b,c,d,a,d) end) clip=text:match("\\clip%((.-)%)") clip=clip.." " h_clip=clip for h=1,clone_h-1 do hc=clip:gsub("([%d%-]+) ([%d%-]+)",function(a,b) return a+dist_h*h.." "..b end) h_clip=h_clip..hc end fullclip=h_clip for v=1,clone_v-1 do if v%2==1 then offset=ccshift else offset=0 end vc=h_clip:gsub("([%d%-]+) ([%d%-]+)",function(a,b) return a+offset.." "..b+dist_v*v end) fullclip=fullclip..vc end fullclip=fullclip:gsub(" $","") text=text:gsub("\\clip%((.-)%)","\\clip("..fullclip..")") end

-- DISSOLVE Individual Lines ------if res.stuff=="dissolve text" then

fullklip="" -- radius of clips based on # of sel. lines and shapes r=math.ceil(z*linez/#sel-1) if shape=="diamond" and not alternate then r=math.floor(r*1.5) end if shape:match("triangle") and alternate then r=math.floor(r*1.4) end if shape:match("triangle") and not alternate then r=math.floor(r*1.5) end if shape=="wave/hexagram" then r=math.floor(r*1.55) end xpt=0 sw=0 osq=0

if not dis2 and not shape:match("lines") and r>0 then for w=1,#allpoints do pt=allpoints[w]

if shape=="square" or shape=="square 2" then krip="m "..pt[1]-r.." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]-r.." "..pt[1]+r.." "..pt[2]+r.." "..pt[1]-r.." "..pt[2]+r.." " end

if shape=="diamond" then krip="m "..pt[1].." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2].." "..pt[1].." "..pt[2]+r.." "..pt[1]-r.." "..pt[2].." " end

if shape=="triangle 1" then krip="m "..pt[1].." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]+r.." "..pt[1]-r.." "..pt[2]+r.." " end

if shape=="triangle 2" then krip="m "..pt[1]-r.." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]-r.." "..pt[1].." "..pt[2]+r.." " end

if shape=="hexagon" then krip="m "..pt[1].." "..pt[2]-2*r.." l "..pt[1]+2*r.." "..pt[2]-r.." "..pt[1]+2*r.." "..pt[2]+r.." "..pt[1].." "..pt[2]+2*r.." "..pt[1]-2*r.." "..pt[2]+r.." "..pt[1]-2*r.." "..pt[2]-r.." " end

if shape=="wave/hexagram" then if sw==0 then krip="m "..pt[1].." "..pt[2]-r+1 .." l "..pt[1]+r.." "..pt[2]+r+1 .." "..pt[1]-r.." "..pt[2]+r+1 .." " else krip="m "..pt[1]-r.." "..pt[2]-r.." l "..pt[1]+r.." "..pt[2]-r.." "..pt[1].." "..pt[2]+r.." " end xpt=xpt+1 if xpt==#xpoints then xpt=0 sw=1-sw end end

fullklip=fullklip..krip end end

if not dis2 and shape=="vertical lines" and r>0 then for w=1,#xpoints do pt=xpoints[w] krip="m "..pt-r.." "..y1.." l "..pt+r.." "..y1.." "..pt+r.." "..y2.." "..pt-r.." "..y2.." " fullklip=fullklip..krip end end

if not dis2 and shape=="horizontal lines" and r>0 then for w=1,#ypoints do pt=ypoints[w] krip="m "..x1-vert.." "..pt-r.." l "..x2+vert.." "..pt-r.." "..x2+vert.." "..pt+r.." "..x1-vert.." "..pt+r.." " fullklip=fullklip..krip end end

if dis2 and r>0 then fullklip=dis2tab[r] end

fullklip=fullklip:gsub(" $","")

text=text:gsub("\\clip%(.-%)","") if r>0 then text=addtag("\\iclip("..fullklip..")",text) end end -- DISSOLVE END 2 ------

-- RANDOMIZED TRANSFORMS ------if res.stuff=="randomized transforms" then dur=line.end_time-line.start_time if RTMax then MxF=dur end

-- Fade/Duration if RTM=="FD" then FD=math.random(MnF,MxF) if RTD and not RTin then line.end_time=line.start_time+FD end if RTD and RTin then line.start_time=line.end_time-FD end if RTF and not RTin then text="{\\fad(0,"..FD..")}"..text text=text:gsub(FD.."%)}{",FD..")") end if RTF and RTin then text="{\\fad("..FD..",0)}"..text text=text:gsub(",0%)}{",",0)") end end

-- Number Transform if RTM=="NT" then NT=math.random(MnT*10,MxT*10)/10 if RTA then NTA=math.random(MnA*10,MxA*10)/10 axel=NTA.."," else axel="" end text=addtag("\\t("..axel.."\\"..RTT..NT..")",text) end

-- Colour Transform if RTM=="CT" then CTfull="" for c=1,#rtcol do ctype="\\"..rtcol[c].."c" ctype=ctype:gsub("\\1c","\\c") Bluu,Grin,Rett=text:match("^{[^}]-"..ctype.."&H(%x%x)(%x%x)(%x%x)&") if Bluu~=nil then R=tonumber(Rett,16) G=tonumber(Grin,16) B=tonumber(Bluu,16) Red=math.random(R-MxC,R+MxC) Green=math.random(G-MxC,G+MxC) Blue=math.random(B-MxC,B+MxC) else Red=math.random(0,255) Green=math.random(0,255) Blue=math.random(0,255) end CT=ctype.."&H"..tohex(Blue)..tohex(Green)..tohex(Red).."&" CTfull=CTfull..CT end if RTA then NTA=math.random(MnA*10,MxA*10)/10 axel=NTA.."," else axel="" end if CTfull~="" then text=addtag("\\t("..axel..CTfull..")",text) end end

-- Move X if rez.rtmx then MMX=math.random(MnX,MxX) text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) if RTin then a=a+MMX else c=c+MMX end return "\\move("..a..","..b..","..c..","..d end) text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)", function(a,b) a2=a if RTin then a=a+MMX else a2=a2+MMX end return "\\move("..a..","..b..","..a2..","..b end) end

-- Move Y if rez.rtmy then MMY=math.random(MnY,MxY) text=text:gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) if RTin then b=b+MMY else d=d+MMY end return "\\move("..a..","..b..","..c..","..d end) text=text:gsub("\\pos%(([%d%.%-]+),([%d%.%-]+)", function(a,b) b2=b if RTin then b=b+MMY else b2=b2+MMY end return "\\move("..a..","..b..","..a..","..b2 end) end end if res.stuff=="time by frames" and z>1 then line.start_time=fr2ms(fstart+(z-1)*frs) line.end_time=fr2ms(fendt+(z-1)*fre) end

-- DUPLICATE AND SHIFT LINES if res.stuff=="duplicate and shift lines" then SF=ms2fr(line.start_time) EF=ms2fr(line.end_time) l2=line effect=line.effect for x=FALL,1,-1 do F=x-FB1 -- Main line if F==0 then l2.start_time=fr2ms(SF) l2.end_time=fr2ms(EF) l2.effect=effect.."[0]" l2.text=text end -- Before if F<0 then l2.start_time=fr2ms(SF+F) l2.end_time=fr2ms(SF+F+1) l2.effect=effect.."["..F.."]" l2.text=text :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+).-%)","\\pos(%1,%2)") :gsub("\\t%b()","") :gsub("\\fad%b()","") end -- After if F>0 then l2.start_time=fr2ms(EF+F-1) l2.end_time=fr2ms(EF+F) l2.effect=effect.."["..F.."]" l2.text=text :gsub("\\move%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+).-%)","\\pos(%3,%4)") :gsub("\\t%b()",function(t) return t:gsub("\\t%([^\\]*",""):gsub("%)$","") end) :gsub("\\fad%b()","") l2.text=l2.text:gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) end if res.rep3=="0" then l2.effect=effect end subs.insert(sel[#sel]+1,l2) end end if res.stuff=="fix kara tags for fbf lines" then KOD=line.start_time-KO1 if KOD>0 then LINE={} for k in text:gmatch("%b{}[^{]*") do table.insert(LINE,k) end for K=1,#LINE do seg=LINE[K] seg=seg:gsub("^(.-)(\\k[of]?)([%d%.]+)(.-)$",function(s,t,k,e) k=k*10 KOD=KOD-k if KOD>=0 then return s..e end if KOD<0 and k+KOD>0 then k=(k+KOD)/10 return s..t..k..e end if KOD<0 and k+KOD<0 then k=k/10 return s..t..k..e end end) LINE[K]=seg:gsub("{}","") end nt="" for K=1,#LINE do nt=nt..LINE[K] end text=nt end end

-- FADEWORKS -- if res.stuff=="fadeworks" then startf=ms2fr(line.start_time) endf=ms2fr(line.end_time) styleref=stylechk(subs,line.style) if not text:match("\\pos%(") then text=getpos(subs,text) end pX,pY=text:match("\\pos%(([^,]+),([^,]+)%)") if rez.Fade then f1,f2=text:match("\\fad%(([^,]+),([^,]+)%)") if not f1 then t_error("Abort: No \\fad tag on line "..z,1) end rez.LFram=ms2fr(f1) rez.RFram=ms2fr(f2) else if rez.LFad then f1=fr2ms(rez.LFram) else f1=nil end if rez.RFad then f2=fr2ms(rez.RFram) else f2=nil end end -- replicating OUT if rez.RF>0 then for r=rez.RF,1,-1 do l2=line posx=numgrad(pX,pX+rez.RFX,rez.RF+1,r+1,rez.RFacx) posy=numgrad(pY,pY+rez.RFY,rez.RF+1,r+1,rez.RFacy) text2=text:gsub("\\pos%b()","\\pos("..posx..","..posy..")") nontra=text2:gsub("\\t%b()","") if rez.Fade and rez.RFtra:len()>1 then text2=text2:gsub("^({\\[^}]-)}","%1\\t("..rez.RFtra..")}") end if rez.RFad then text2=text2:gsub("\\fad%b()",""):gsub("^{","{\\fad(0,"..f2..")") end if rez.Rfbf:len()>1 then for tag in rez.Rfbf:gmatch("\\[^\\]+") do tg,val=tag:match("(\\%d?%a+)([%d&%-][^\\}]*)") endval=nontra:match(tg.."([%d&%-][^\\}]*)") or styleval(tg) if tg=="\\c" or tg:match"%d" then nval=acgrad(val,endval,rez.RF+1,rez.RF-r+1,1/rez.RFact) else nval=numgrad(val,endval,rez.RF+1,rez.RF-r+1,1/rez.RFact) end text2=addtag3(tg..nval,text2) end end if rez.Ralf:len()>1 then ralf=rez.Ralf:match("%x%x") if ralf then endval=nontra:match("alpha&H(%x%x)&") or "00" nval=acgrad(ralf,endval,rez.RF+1,rez.RF-r+1,1/rez.RFact) text2=addtag3("\\alpha"..nval,text2) end end

startf2=endf+rez.RFshift*(r-1) endf2=startf2+rez.RFram if rez.Fade then text2=text2:gsub("\\fad%(([^,]+),([^,]+)%)","\\fad(0,%2)") startf2=startf2-rez.RFram+rez.RFshift endf2=endf2-rez.RFram+rez.RFshift end l2.start_time=fr2ms(startf2) l2.end_time=fr2ms(endf2) l2.text=text2 subs.insert(i+1,l2) nsel=shiftsel2(nsel,i,1) end end line.start_time=fr2ms(startf) line.end_time=fr2ms(endf) line.text=text subs.insert(i+1,line) -- replicating IN if rez.LF>0 then for r=1,rez.LF do l2=line posx=numgrad(pX,pX+rez.LFX,rez.LF+1,r+1,rez.LFacx) posy=numgrad(pY,pY+rez.LFY,rez.LF+1,r+1,rez.LFacy) text2=text:gsub("\\pos%b()","\\pos("..posx..","..posy..")") nontra=text2:gsub("\\t%b()","") if rez.Fade and rez.LFtra:len()>1 then Ltra="{}" for tag in rez.LFtra:gmatch("\\[^\\]+") do tg=tag:match("(\\%d?%a+)[%d&%-]") if nontra:match(tg.."[%d&%-]") then Ltra=Ltra:gsub("{","{"..nontra:match(tg.."[%d&%-][^\\}]*")) else Ltra=fill_in(Ltra,tg) end text2=addtag3(tag,text2) end Ltra=Ltra:gsub("[{}]","") text2=text2:gsub("^({\\[^}]-)}","%1\\t("..Ltra..")}") end if rez.LFad then text2=text2:gsub("\\fad%b()",""):gsub("^{","{\\fad("..f1..",0)") end if rez.Lfbf:len()>1 then for tag in rez.Lfbf:gmatch("\\[^\\]+") do tg,val=tag:match("(\\%d?%a+)([%d&%-][^\\}]*)") endval=nontra:match(tg.."([%d&%-][^\\}]*)") or styleval(tg) endval=tostring(endval) if tg=="\\c" or tg:match"%d" then nval=acgrad(val,endval,rez.LF+1,rez.LF-r+1,1/rez.LFact) else nval=numgrad(val,endval,rez.LF+1,rez.LF-r+1,1/rez.LFact) end text2=addtag3(tg..nval,text2) end end if rez.Lalf:len()>1 then lalf=rez.Lalf:match("%x%x") if lalf then endval=nontra:match("alpha&H(%x%x)&") or "00" nval=acgrad(lalf,endval,rez.LF+1,rez.LF-r+1,1/rez.LFact) text2=addtag3("\\alpha"..nval,text2) end end

endf2=startf-rez.LFshift*(r-1) startf2=endf2-rez.LFram if rez.Fade then text2=text2:gsub("\\fad%(([^,]+),([^,]+)%)","\\fad(%1,0)") startf2=startf2+rez.LFram-rez.LFshift endf2=endf2+rez.LFram-rez.LFshift end l2.start_time=fr2ms(startf2) l2.end_time=fr2ms(endf2) l2.text=text2 subs.insert(i+1,l2) nsel=shiftsel2(nsel,i,1) end end end

line.text=text subs[i]=line if res.stuff=="fadeworks" then subs.delete(i) end if res.stuff=="split into letters" or res.stuff=="explode" and not rez.excom then subs.delete(i) table.remove(sel,#sel) end if res.stuff=="what is the Matrix?" then subs.delete(i) end end

if res.stuff:match"replacer" then aegisub.progress.task("All stuff has been finished.") if repl==1 then rp=" modified line" else rp=" modified lines" end press,reslt=ADD({},{repl..rp},{cancel=repl..rp}) end if res.stuff=="duplicate and shift lines" then SEL=#sel for z=#sel,1,-1 do subs.delete(sel[z]) end for x=1,(FA+FB)*SEL do table.insert(sel,sel[SEL]+x) end end if res.stuff=="fadeworks" then sel=nsel end if res.stuff=="format dates" and rez.log then aegisub.log(datelog) end if noclip then t_error("Some lines weren't processed - missing clip.") noclip=nil end savetab=nil return sel end function fill_in(tags,tag) if tag=="\\bord" then tags=tags:gsub("^{","{"..tag..styleref.outline) elseif tag=="\\shad" then tags=tags:gsub("^{","{"..tag..styleref.shadow) elseif tag=="\\fscx" then tags=tags:gsub("^{","{"..tag..styleref.scale_x) elseif tag=="\\fscy" then tags=tags:gsub("^{","{"..tag..styleref.scale_y) elseif tag=="\\fs" or tag=="\\fsize" then tags=tags:gsub("^{","{"..tag..styleref.fontsize) elseif tag=="\\fsp" then tags=tags:gsub("^{","{"..tag..styleref.spacing) elseif tag=="\\alpha" then tags=tags:gsub("^{","{"..tag.."&H00&") elseif tag=="\\1a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color1:match("H%x%x").."&") elseif tag=="\\2a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color2:match("H%x%x").."&") elseif tag=="\\3a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color3:match("H%x%x").."&") elseif tag=="\\4a" then tags=tags:gsub("^{","{"..tag.."&"..styleref.color4:match("H%x%x").."&") elseif tag=="\\c" then tags=tags:gsub("^{","{"..tag..styleref.color1:gsub("H%x%x","H")) elseif tag=="\\2c" then tags=tags:gsub("^{","{"..tag..styleref.color2:gsub("H%x%x","H")) elseif tag=="\\3c" then tags=tags:gsub("^{","{"..tag..styleref.color3:gsub("H%x%x","H")) elseif tag=="\\4c" then tags=tags:gsub("^{","{"..tag..styleref.color4:gsub("H%x%x","H")) else tags=tags:gsub("^{","{"..tag.."0") end return tags end function styleval(tag) if tag=="\\bord" then s_val=styleref.outline elseif tag=="\\shad" then s_val=styleref.shadow elseif tag=="\\fscx" then s_val=styleref.scale_x elseif tag=="\\fscy" then s_val=styleref.scale_y elseif tag=="\\fs" then s_val=styleref.fontsize elseif tag=="\\fsp" then s_val=styleref.spacing elseif tag=="\\alpha" then s_val="&H00&" elseif tag=="\\1a" then s_val="&"..styleref.color1:match("H%x%x").."&" elseif tag=="\\2a" then s_val="&"..styleref.color2:match("H%x%x").."&" elseif tag=="\\3a" then s_val="&"..styleref.color3:match("H%x%x").."&" elseif tag=="\\4a" then s_val="&"..styleref.color4:match("H%x%x").."&" elseif tag=="\\c" then s_val=styleref.color1:gsub("H%x%x","H") elseif tag=="\\2c" then s_val=styleref.color2:gsub("H%x%x","H") elseif tag=="\\3c" then s_val=styleref.color3:gsub("H%x%x","H") elseif tag=="\\4c" then s_val=styleref.color4:gsub("H%x%x","H") else s_val="0" end return s_val end function shiftsel2(sel,i,mode) if ii then sel[s]=sel[s]+1 end end end if mode==1 then table.insert(sel,i+1) end table.sort(sel) return sel end

-- Jump to Next -- function nextsel(subs,sel) lm=nil i=sel[1] marks={} for x,i in ipairs(sel) do rine=subs[i] txt=rine.text:gsub("%b{}","") if res.field=="text" then mark=txt end if res.field=="style" then mark=rine.style end if res.field=="actor" then mark=rine.actor end if res.field=="effect" then mark=rine.effect end if res.field=="layer" then mark=rine.layer end if mark=="" then mark="_empty_" end if mark~=lm then table.insert(marks,mark) end lm=mark end count=1 repeat line=subs[i+count] txt2=line.text:gsub("%b{}","")

if res.field=="text" then hit=txt2 end if res.field=="style" then hit=line.style end if res.field=="actor" then hit=line.actor end if res.field=="effect" then hit=line.effect end if res.field=="layer" then hit=line.layer end if hit=="" then hit="_empty_" end ch=0 for m=1,#marks do if marks[m]==hit then ch=1 end end if ch==0 or i+count==#subs then sel={i+count} end count=count+1 until ch==0 or hit==nil or i+count>#subs return sel end

-- Alpha Shift -- function alfashift(subs,sel) count=1 for x, i in ipairs(sel) do line=subs[i] text=line.text aa=re.find(text,"\\{\\\\alpha\\&HFF\\&\\}[\\w[:punct:]]") if not aa then t_error("Line "..x.." does not \nappear to have \n\\alpha&&HFF&&",true) end if count>1 then switch=1 repeat text=re.sub(text,"(\\{\\\\alpha\\&HFF\\&\\})([\\w[:punct:]])","\\2\\1") text=text :gsub("({\\alpha&HFF&}) "," %1") :gsub("({\\alpha&HFF&})\\N","\\N%1") :gsub("({\\alpha&HFF&})$","") switch=switch+1 until switch>=count end count=count+1 line.text=text subs[i]=line end end

-- Merge tags -- function merge(subs,sel) tk={} tg={} stg="" for x, i in ipairs(sel) do line=subs[i] text=line.text text=text:gsub("{\\\\k0}","") repeat text,c=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until c==0 vis=text:gsub("%b{}","") if x==1 then rt=vis ltrmatches=re.find(rt,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end end if vis~=rt then t_error("Error. Inconsistent text.",true) end stags=text:match("^{(\\[^}]-)}") or "" stg=stg..stags stg=duplikill(stg) text=text:gsub("^{\\[^}]-}","") :gsub("{[^\\}]-}","") count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t newt=duplikill(newt) newt=newt:gsub("%*$","") end end if newt~="" then newline=newline.."{"..newt.."}" end end newtext="{"..stg.."}"..newline newtext=extrakill(newtext,2) line=subs[sel[1]] line.text=newtext subs[sel[1]]=line for i=#sel,2,-1 do subs.delete(sel[i]) end sel={sel[1]} return sel end function textmod(orig,text) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until not text:match("{(\\[^}]-)}{(\\[^}]-)}") vis=text:gsub("%b{}","") ltrmatches=re.find(vis,".") for l=1,#ltrmatches do table.insert(tk,ltrmatches[l].str) end stags=text:match(STAG) or "" text=text:gsub(STAG,"") :gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext=stags..newline text=newtext:gsub("{}","") return text end

-- Honorificslaughterhouse -- function honorifix(subs,sel) for i=#subs,1,-1 do if subs[i].class=="dialogue" then line=subs[i] line.text=line.text :gsub("%-san","{-san}") :gsub("%-chan","{-chan}") :gsub("%-kun","{-kun}") :gsub("%-sama","{-sama}") :gsub("%-niisan","{-niisan}") :gsub("%-oniisan","{-oniisan}") :gsub("%-oniichan","{-oniichan}") :gsub("%-oneesan","{-oneesan}") :gsub("%-oneechan","{-oneechan}") :gsub("%-neesama","{-neesama}") :gsub("%-sensei","{-sensei}") :gsub("%-se[mn]pai","{-senpai}") :gsub("%-dono","{-dono}") :gsub("Onii{%-chan}","Brother{Onii-chan}") :gsub("Onii{%-san}","Brother{Onii-san}") :gsub("Onee{%-chan}","Sister{Onee-chan}") :gsub("Onee{%-san}","Sister{Onee-san}") :gsub("Onee{%-sama}","Sister{Onee-sama}") :gsub("onii{%-chan}","brother{onii-chan}") :gsub("onii{%-san}","brother{onii-san}") :gsub("onee{%-chan}","sister{onee-chan}") :gsub("onee{%-san}","sister{onee-san}") :gsub("onee{%-sama}","sister{onee-sama}") :gsub("{{","{") :gsub("}}","}") :gsub("({[^{}]-){(%-%a-)}([^{}]-})","%1%2%3") subs[i]=line end end end

-- framerate -- function framerate(subs) f1=res.fps1 f2=res.fps2 for i=1, #subs do if subs[i].class=="dialogue" then local line=subs[i] line.start_time=line.start_time/f2*f1 line.end_time=line.end_time/f2*f1 subs[i]=line end end end

-- reanimatools -- function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") val="[%d%-&]" if not tgt then tgt=tg:match("(\\%d?%a+)%b()") val="%b()" end if not tgt then tgt=tg:match("\\fn") val="" end if not tgt then t_error("adding tag '"..tg.."' failed.") end if tgt:match("clip") then txt,r=txt:gsub("^({[^}]-)\\i?clip%b()","%1"..tg) if r==0 then txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end elseif no_tf:match("^({[^}]-)"..tgt..val) then txt=txt:gsub("^({[^}]-)"..tgt..val.."[^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end function string2line(str) local ltype,layer,s_time,e_time,style,actor,margl,margr,margv,eff,txt=str:match("(%a+): (%d+),([^,]-),([^,]-),([^,]- ),([^,]-),([^,]-),([^,]-),([^,]-),([^,]-),(.*)") l2={} l2.class="dialogue" if ltype=="Comment" then l2.comment=true else l2.comment=false end l2.layer=layer l2.start_time=string2time(s_time) l2.end_time=string2time(e_time) l2.style=style l2.actor=actor l2.margin_l=margl l2.margin_r=margr l2.margin_t=margv l2.effect=eff l2.text=txt return l2 end function string2time(timecode) timecode=timecode:gsub("(%d):(%d%d):(%d%d)%.(%d%d)",function(a,b,c,d) return d*10+c*1000+b*60000+a*3600000 end) return timecode end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function cleantr(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..trnsfrm.."}") return tags end function numgrad(V1,V2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc VC=round(acc_fac*(V2-V1)+V1,2) return VC end function acgrad(C1,C2,total,l,acc) acc=acc or 1 acc_fac=(l-1)^acc/(total-1)^acc B1,G1,R1=C1:match("(%x%x)(%x%x)(%x%x)") B2,G2,R2=C2:match("(%x%x)(%x%x)(%x%x)") A1=C1:match("(%x%x)") R1=R1 or A1 A2=C2:match("(%x%x)") R2=R2 or A2 nR1=(tonumber(R1,16)) nR2=(tonumber(R2,16)) R=acc_fac*(nR2-nR1)+nR1 R=tohex(round(R)) CC="&H"..R.."&" if B1 then nG1=(tonumber(G1,16)) nG2=(tonumber(G2,16)) nB1=(tonumber(B1,16)) nB2=(tonumber(B2,16)) G=acc_fac*(nG2-nG1)+nG1 B=acc_fac*(nB2-nB1)+nB1 G=tohex(round(G)) B=tohex(round(B)) CC="&H"..B..G..R.."&" end return CC end function tohex(num) n1=math.floor(num/16) n2=num%16 num=tohex1(n1)..tohex1(n2) return num end function tohex1(num) HEX={"1","2","3","4","5","6","7","8","9","A","B","C","D","E"} if num<1 then num="0" elseif num>14 then num="F" else num=HEX[num] end return num end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function getpos(subs,text) for g=1,#subs do if subs[g].class=="info" then local k=subs[g].key local v=subs[g].value if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if resx==nil then resx=0 end if resy==nil then resy=0 end if subs[g].class=="style" then local st=subs[g] if st.name==line.style then acleft=st.margin_l if line.margin_l>0 then acleft=line.margin_l end acright=st.margin_r if line.margin_r>0 then acright=line.margin_r end acvert=st.margin_t if line.margin_t>0 then acvert=line.margin_t end acalign=st.align if text:match("\\an%d") then acalign=text:match("\\an(%d)") end aligntop="789" alignbot="123" aligncent="456" alignleft="147" alignright="369" alignmid="258" if alignleft:match(acalign) then horz=acleft h_al="left" elseif alignright:match(acalign) then horz=resx-acright h_al="right" elseif alignmid:match(acalign) then horz=resx/2 h_al="mid" end if aligntop:match(acalign) then vert=acvert v_al="top" elseif alignbot:match(acalign) then vert=resy-acvert v_al="bottom" elseif aligncent:match(acalign) then vert=resy/2 v_al="mid" end break end end end if horz>0 and vert>0 then if not text:match("^{\\") then text="{\\rel}"..text end text=text:gsub("^({\\[^}]-)}","%1\\pos("..horz..","..vert..")}") :gsub("\\rel","") end return text end function progress(msg) if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title(msg) end function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end

-- Config Stuff -- function saveconfig() unconf="Unimportant Configuration\n\n" for key,val in ipairs(unconfig) do if val.class=="floatedit" or val.class=="dropdown" then unconf=unconf..val.name..":"..res[val.name].."\n" end if val.class=="checkbox" and val.name~="save" then unconf=unconf..val.name..":"..tf(res[val.name]).."\n" end end unimpkonfig=ADP("?user").."\\unimportant.conf" file=io.open(unimpkonfig) if file~=nil then konf=file:read("*all") io.close(file) imp1=konf:match("imp1:(.-)\n") imp2=konf:match("imp2:(.-)\n") imp3=konf:match("imp3:(.-)\n") chap1=konf:match("chap1:(.-)\n") chap2=konf:match("chap2:(.-)\n") chap3=konf:match("chap3:(.-)\n") end if imp1==nil then imp1="relative" end if imp2==nil then imp2="" end if imp3==nil then imp3="D:\\typesetting\\" end if chap1==nil then chap1="relative" end if chap2==nil then chap2="" end if chap3==nil then chap3="D:\\typesetting\\" end

savestuff={ {x=0,y=0,class="label",label="Import script path:"}, {x=0,y=1,class="label",label="Import relative path:"}, {x=0,y=2,class="label",label="Import absolute path:"}, {x=0,y=3,class="label",label="Chapters save path:"}, {x=0,y=4,class="label",label="Chapters relative path:"}, {x=0,y=5,class="label",label="Chapters absolute path:"}, {x=1,y=0,class="dropdown",name="imp1",items={"relative","absolute"},value=imp1}, {x=1,y=1,class="edit",width=16,name="imp2",value=imp2}, {x=1,y=2,class="edit",width=16,name="imp3",value=imp3}, {x=1,y=3,class="dropdown",name="chap1",items={"relative","absolute"},value=chap1}, {x=1,y=4,class="edit",width=16,name="chap2",value=chap2}, {x=1,y=5,class="edit",width=16,name="chap3",value=chap3}, }

click,rez=ADD(savestuff,{"Save","Cancel"},{ok='Save',close='Cancel'}) if click=="Cancel" then ak() end rez.imp3=rez.imp3:gsub("[^\\]$","%1\\") rez.chap3=rez.chap3:gsub("[^\\]$","%1\\")

for key,val in ipairs(savestuff) do if val.x==1 then unconf=unconf..val.name..":"..rez[val.name].."\n" end end file=io.open(unimpkonfig,"w") file:write(unconf) file:close() ADD({{class="label",label="Config saved to:\n"..unimpkonfig}},{"OK"},{close='OK'}) end function loadconfig() unimpkonfig=ADP("?user").."\\unimportant.conf" file=io.open(unimpkonfig) if file~=nil then konf=file:read("*all") io.close(file) if konf:match("^%-%-") then konf="" t_error("Your config file is outdated.\nUse the 'Save Config' button to save a new one.") else for key,val in ipairs(unconfig) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="dropdown" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end if lastimp and val.name=="stuff" then val.value=lastuff end if lastimp and val.name=="log" then val.value=lastlog end if lastimp and val.name=="zeros" then val.value=lastzeros end if lastimp and val.name=="field" then val.value=lastfield end end end end script_path=konf:match("imp1:(.-)\n") or "relative" relative_path=konf:match("imp2:(.-)\n") or "" absolute_path=konf:match("imp3:(.-)\n") or "D:\\typesetting\\" ch_script_path=konf:match("chap1:(.-)\n") or "relative" ch_relative_path=konf:match("chap2:(.-)\n") or "" ch_absolute_path=konf:match("chap3:(.-)\n") or "D:\\typesetting\\" end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function analyze(l) text=l.text dur=l.end_time-l.start_time dura=dur/1000 txt=text:gsub("%b{}","") :gsub("\\N","") visible=text:gsub("{\\alpha&HFF&}[^{}]-{[^{}]-}","") :gsub("{\\alpha&HFF&}[^{}]*$","") :gsub("{[^{}]- }","") :gsub("\\[Nn]","*") :gsub("%s?%*+%s?"," ") :gsub("^%s+","") :gsub("%s+$","") wrd=0 for word in txt:gmatch("([%a\']+)") do wrd=wrd+1 end chars=visible:gsub(" ","") :gsub("[%.,\"]","") char=chars:len() cps=math.ceil(char/dura) if dur==0 then cps=0 end end function info(subs,sel,act) styletab={} dc=0 sdur=0 S=subs[sel[1]].start_time E=subs[sel[#sel]].end_time video=nil stitle=nil colorspace=nil resx=nil resy=nil prop=aegisub.project_properties() for x,i in ipairs(sel) do line=subs[i] dur=line.end_time-line.start_time if line.start_timeE then E=line.end_time end sdur=sdur+dur end seldur=sdur/1000 for i=1, #subs do if subs[i].class=="info" then local k=subs[i].key local v=subs[i].value if k=="Title" then stitle=v end if k=="Video File" then video=v end if k=="YCbCr Matrix" then colorspace=v end if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if video==nil then video=prop.video_file:gsub("^.*\\","") end if stitle==nil then sct="" else sct="Script title: "..stitle.."\n" end if video==nil then vf="" else vf="Video file: "..video.."\n" end if resy==nil then reso="" else reso="Script resolution: "..resx.."x"..resy.."\n" end if colorspace==nil then cols="" else cols="Colorspace: "..colorspace.."\n" end nfo=sct..vf..reso..cols if subs[i].class=="style" then local s=subs[i] table.insert(styletab,s) end if subs[i].class=="dialogue" then dc=dc+1 local l=subs[i] if i==act then ano=dc analyze(l) for s=1,#styletab do st=styletab[s] if st.name==l.style then acfont=st.fontname acsize=st.fontsize acalign=st.align acleft=st.margin_l acright=st.margin_r acvert=st.margin_t acbord=st.outline acshad=st.shadow if st.bold then actbold="Bold" else actbold="Regular" end end end aligntop="789" alignbot="123" aligncent="456" alignleft="147" alignright="369" alignmid="258" if aligntop:match(acalign) then vert=acvert elseif alignbot:match(acalign) then vert=resy-acvert elseif aligncent:match(acalign) then vert=resy/2 end if alignleft:match(acalign) then horz=acleft elseif alignright:match(acalign) then horz=resx-acright elseif alignmid:match(acalign) then horz=resx/2 end

aktif="Active line: "..ano.."\nStyle used: "..l.style.."\nFont used: "..acfont.."\nWeight: "..actbold.."\nFont size: "..acsize.."\nBorder: "..acbord.."\nShadow: "..acshad.."\nDuration: "..dura.."s\nCharacters: "..char.."\nCharacters per second: "..cps.."\nDefault position: "..horz..","..vert.."\n\nVisible text:\n"..visible end end

end infodump=nfo.."Styles used: "..#styletab.."\nDialogue lines: "..dc..", Selected: "..#sel.."\nCombined length of selected lines: "..seldur.."s\nSelection duration: "..(E-S)/1000 .."s\n\n"..aktif end help_i=[[ - IMPORT/EXPORT -

This allows you to import OP/ED or signs (or whatever) from an external .ass file. OP/ED must be saved as OP.ass and ED.ass; a sign can have any name. The .ass file may contain headers, or it can be just the dialogue lines. The imported stuff will be shifted to your currently selected line (or the first one in your selection). The first line of the saved file works as a reference point, so use a "First frame of OP" line etc. (You can save your OP/ED shifted to 0 or you can just leave it as is; the times will be recalculated to start at the current line.) "keep line" will keep your current line and comment it. Otherwise the line gets deleted.

IMPORT SIGN / IMPORT SIGNS - works like OP/ED, but you have to input the sign's name. The difference between the two is: SIGN - each sign must be saved in its own .ass file. In the GUI, input the sign's/file's name, for example "eptitle"[.ass]. SIGNS - all signs must be saved in signs.ass. They are distinguished by what's in the "effect" field - that's the sign's name. For SIGN, make something like eptitle.ass, eyecatch.ass; for SIGNS, put "eptitle" or "eyecatch" in the effect field, and put all the signs in signs.ass. (You can have blank lines between signs for clarity. The script can deal with those.) The GUI will then show you a list of signs that it gets from the effect fields. I recommend using SIGNS, as it's imo more efficient (but SIGN was written first and I didn't nuke it).

Options: With nothing checked, stuff is shifted to the first frame of your active line (like OP/ED). (SIGN) File name: "custom" will use what you type below. The other ones are presets. "keep current line's times" - all imported lines will have the start/end time of your active line "keep current line's text" - all imported lines will have their text (not tags) replaced with your active line's text - If you want to replace only some lines and keep others, like masks, put 'x' in actor field of the mask. "combine tags (current overrides)" - tags from current + imported line get combined (current overrides imported) "combine tags (imported overrides)" - same as above, but imported overrides current - Both of these will also be ignored for imported lines that have "x" in actor field. "don't shift times" - times of imported lines will be kept as they were saved "delete original line" - this overrides the "keep line" option in the main menu. (I thought it would be convenient to have it here.)

EXPORT SIGN - Saves the selected sign(s) either to 'signs.ass' or to a new file. Effect field must contain the signs' names.

You can use relative or absolute paths. (Check the settings below.) Default is the script's folder. If you want the default to be one folder up, use "..\". You can use an absolute path, have one huge signs.ass there, and have all the signs marked "show_name-sign_name" in the effect field.

IMPORT CHPTRS - Imports chapters from xml files - creates lines with "chptr" in actor and {ch. name} as text]] help_u=[[ UPDATE LYRICS

This is probably the most complicated part, but if your songs have some massive styling with layers + tracking, this will make updating lyrics, which would otherwise be a pain in the ass, really easy. The only styling that will prevent this from working is inline tags - gradient by character etc.

The prerequisite here is that your OP/ED MUST have NUMBERED lines! (See NUMBERS section - might be good to read that first.) The numbers must correspond to the verses, not to lines in the script. If line 1 of the SONG is mocha-tracked over 200 frames, all of those frames must be numbered 01. It is thus most convenient to number the lines before you start styling, when it's still simple.

How this works: Paste your updated lyrics into the large, top-left area of the GUI. Use the Left and Right fields to set the markers to detect the right lines. Without markers it will just look for numbers. If your OP lines are numbered with "OP01eng", you must set "OP" under Left and "eng" under Right. For now, everything is case-sensitive (I might change that later if it gets really annoying and pointless). You must also correctly set the actor/effect choice in the bottom-right part of the GUI. If you pasted lyrics, selected "update lyrics", and set markers and actor/effect. Then hit Import, and lyrics will be updated.

How it works - example: The lyrics you pasted in the data box get their lines assigned with numbers from 1 to whatever. Let's say your markers are "OP01eng" and you're using the effect field. The script looks for lines with that pattern in the effect field. When it finds one, it reads the number (for example "01" from "OP01eng") and replaces the line's text (skipping tags) with line 1 from the pasted lyrics. For every line marked "OP##eng" it replaces the current lyrics with line ## from your pasted updated lyrics.

To make sure this doesn't fuck up tremendously, it shows you a log with all replacements at the end.

That's pretty much all you really need to know for updating lyrics, but there are a few more things.

If the script doesn't find any lines that match the markers, it gives you a message like this: "The effect field of selected lines doesn't match given pattern..." This means the lines either don't exist in your selection, or you probably forgot to set the markers.

"style restriction" is an extra option that lets you limit the replacing to lines whose style contains given pattern. Let's give some examples: You check the restriction and type "OP" in the field below. You can now select the whole script instead of selecting only the OP lines, and only lines with "OP" in style will be updated. You may have the ED numbered the same way, but the "OP" restriction will ignore it. This can be also useful if you have lines numbered just 01, 02 etc., and you have english and romaji, all mixed together. If your styles are OP-jap and OP-eng, you can type "jap" in the restriction field if you're updating romaji to make sure the script doesn't update the english lines as well (replacing them with romaji). It is, however, recommended to just use different markers, like j01 / e01.]] help_c=[[ - CHAPTERS -

This will generate chapters from the .ass file

MARKER: For a line to be used for chapters, it has to be marked with "chapter"/"chptr"/"chap" in actor/effect field (depending on settings) or the same 3 options as a separate comment, ie. {chapter} etc.

CHAPTER NAME: What will be used as chapter name. It's either the content of the effect field, or the line's FIRST comment. If the comment is {OP first frame} or {ED start}, the script will remove " first frame" or " start", so you can keep those.

If you use default settings, just put "chapter" in actor field and make comments like {OP} or {Part A}.

Subchapters: You can make subchapters like this {Part A::Scene 5}. This will be a subchapter of "Part A" called "Scene 5".

If you want a different LANGUAGE than 'eng', set it in the textbox below "chapter mark"

CHAPTER MARK: Sets the selected chapter for selected line(s). Uses marker and name. (Doesn't create xml.) If you want a custom chapter name, type it in the textbox below this. mp4-compatible chapters: switches to this format: CHAPTER01=00:00:00.033 CHAPTER01NAME=Intro]] help_n=[[ - NUMBERS -

This is a tool to number lines and add various markers to actor/effect fields. The dropdown with "01" lets you choose how many leading zeros you want. The Left and Right fields will add stuff to the numbers. If Left is "x" and Right is "yz", the first marker will be "x01yz". What makes this function much more versatile is the "Mod" field. If you put in one number, then that's the number from which the numbering will start, so "5" -> 5, 6, 7, etc. You can, however, use a comma or slash to modify the numbering some more. "8,3" or "8/3" will start numbering from 8, repeating each number 3 times, so 8, 8, 8, 9, 9, 9, 10, 10, 10, etc. This allows you to easily number lines that are typeset in layers etc. Additionally, you can set a limit in [], for example 1/3[2], which will start from 1, use each number 3 times, and only go up to 2 and then start again, so: 1 1 1 2 2 2 1 1 1 2 2 2 2/3[4] would give you 2 2 2 3 3 3 4 4 4 2 2 2 3 3 3 4 4 4 ... "add to marker" uses the Left and Right fields to add stuff to the current content of actor/effect/text. If you number lines for the OP, you can set "OP-" in Left and "-eng" in Right to get "OP-01-eng". (Mod does nothing when adding markers.)]] help_d=[[ - DO STUFF -

- Save/Load - You can use this to save for example bits of text you need to paste frequently (like a multi-clipboard). Paste text in the data area to save it. If the data area is empty, the function will load your saved texts.

- Replacer + Lua Patterns - Use "Left" and "Right" for a lua regexp replace function.

- Replacer + Perl Regexp - Use "Left" and "Right" for a perl regexp replace function.

- Lua Calc - Use "Left" and "Right" with lua regexp to perform calculations on captured numbers. Captures will be named a, b, c... up to p (16 captures max). Functions are +, -, *, /, and round(a), which rounds the number captured in a. > Example: (%d)(%d)(%d) -> a+1b*2c-3 This will match 3-digit patterns, add 1 to first digit, multiply the second by 2, and subtract 3 from the 3rd. If you want to leave one of the captures as is, use .. to separate it from other letters: a+1b..c-3 > Example: pos%(([%d%.]+),([%d%.]+) -> pos(a+50,b-100 This will shift position right by 50 and up by 100.

- Alpha Shift - Shifts {\alpha&HFF&} by one letter for each line. Text thus appears letter by letter. It's an alternative to the script that spawns \ko, but this works with shadow too. Duplicate a line with {\alpha&HFF&} however many times you need and run the script on the whole selection.

- Motion Blur - Creates motion blur by duplicating the line and using some alpha. By default you keep the existing blur for each line, but you can set a value to override all lines. 'Distance' is the distance between the \pos coordinates of the resulting 2 lines. If you use 3 lines, the 3rd one will be in the original position, i.e. in the middle. The direction is determined from the first 2 points of a vectorial clip (like with clip2frz/clip2fax).

- Merge Tags - Select lines with the same text but different tags, and they will be merged into one line with tags from all of them. For example: {\bord2}AB{\shad3}C A{\fs55}BC -> {\bord2}A{\fs55}B{\shad3}C If 2 lines have the same tag in the same place, the value of the later line overrides the earlier one.

- Jump to Next - This is meant to get you to the "next sign" in the subtitle grid. When mocha-tracking 1000+ lines, it can be a pain in the ass to find where one sign ends and another begins. Select lines that belong to the current "sign", ie. different layers/masks/texts. The script will search for the first line in the grid that doesn't match any of the selected ones, based on the "Marker". - Reverse Text - Reverses text (character by character). Nukes comments and inline tags.

- Reverse Words - Reverses text (word by word). Nukes comments and inline tags.

- Reverse Transforms - \blur1\t(\blur3) becomes \blur3\t(\blur1). Only for initial tags. Only one transform for each tag.

- Reverse FBF Times - Reverses timing for fbf lines so that last frame becomes the first and vice versa.

- Fake Capitals - Creates fake capitals by increasing font size for first letters. With all caps, for first letters of words. With mixed text, for uppercase letters. Set the \fs for the capitals in the Left field. Looks like this: {\fs60}F{\fs}AKE {\fs60}C{\fs}APITALS

- Format Dates - Formats dates to one of 4 options. Has its own GUI. Only converts from the other 3 options in the GUI.

- Split into Letters - Makes a line for each letter, making the other letters invisible with alpha. This lets you do things with each letter separately.

- Explode - This splits the line into letters and makes each of them move in a different direction and fade out.

- Dissolve Text - Various modes of dissolving text. Has its own Help.

- Randomized Transforms - Various modes of randomly transforming text. Has its own Help.

- Clone Clip - Clones/replicates a clip you draw. Set how many rows/columns and distances between them, and you can make large patterns.

- Duplicate and Shift Lines - Duplicates selected lines as many times you want before and/or after the current line. Use Left/Right fields to set how many frames should be duplicated before/after the line. \move and \t --> lines before get \pos with start coordinates and state before transforms; lines after get end coordinates and state after transforms. Lines are automatically numbered in Effect field. You can disable that by typing 0 in Mod field.

- FadeWorks - Creates complex fade effects. Check the manual on the website, which includes .ass examples. http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#import

- Time by Frames - Left = frames to shift start time by, each line (2 = each new line starts 2 frames later than previous) Right = frames to shift end time by, each line (4 = each new line ends 4 frames later than previous) - Convert Framerate - Converts framerate from a to b where a is the input from "Left" and b is input from "Right".

- Fix Kara Tags for fbf Lines - If you need to split a line with kara tags, this adjusts the tags so that the text appears continuously as it should. Selection must include the first line for reference. Applies to all karaoke tags indiscriminately - \k, \kf, \ko.

- Make Style from Act. Line - Creates a new style from the values in start tags in active line combined with the current style.

- Make Comments Visible - Nukes { } from comments, thus making them part of the text visible on screen.

- Switch Commented/Visible - Comments out what's visible and makes visible what's commented. Allows switching between two texts.

- Honorificslaughterhouse - Comments out honorifics.]]

-- Significance GUI ------function significance(subs,sel,act) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" COMM="{[^\\}]-}" aegisub.progress.title("Loading...") aegisub.progress.task("This should take less than a second, so you won't really read this.") if datata==nil then data="" else data=datata end if sub1==nil then sub1="" end if sub2==nil then sub2="" end if sub3==nil then sub3=1 end msg={"If it breaks, it's your fault.","This should be doing something...","Breaking your computer. Please wait.","Unspecified operations in progress.","This may or may not work.","Trying to avoid bugs...","Zero one one zero one zero...","10110101001101101010110101101100001","I'm surprised anyone's using this","If you're seeing this for too long, it's a bad sign.","This might hurt a little.","Please wait... I'm pretending to work.","Close all your programs and run."} rm=math.random(1,#msg) msge=msg[rm] if lastimp then dropstuff=lastuff lok=lastlog zerozz=lastzeros fld=lastfield else dropstuff="replacer" lok=false zerozz="01" fld="effect" end g_impex={"import OP","import ED","import sign","import signs","export sign","import chptrs","update lyrics"} g_stuff={"save/load","replacer","lua calc","alpha shift","motion blur","merge tags","jump to next","reverse text","reverse words","reverse transforms","reverse fbf times","fake capitals","format dates","split into letters","explode","dissolve text","randomized transforms","what is the Matrix?","clone clip","duplicate and shift lines","fadeworks","time by frames","convert framerate","transform \\k to \\t\\alpha","fix kara tags for fbf lines","make style from act. line","make comments visible","switch commented/visible","honorificslaughterhouse"} unconfig={ -- Sub -- {x=0,y=16,width=3,height=1,class="label",label="Left "}, {x=3,y=16,width=3,height=1,class="label",label="Right "}, {x=6,y=16,width=3,height=1,class="label",label="Mod "}, {x=0,y=17,width=3,height=1,class="edit",name="rep1",value=sub1}, {x=3,y=17,width=3,height=1,class="edit",name="rep2",value=sub2}, {x=6,y=17,width=3,height=1,class="edit",name="rep3",value=sub3,hint="Numbers: start/repeat[limit]\nreplacer/lua calc: limit"},

-- import {x=9,y=3,width=2,height=1,class="label",label="Import/Export"}, {x=9,y=4,width=2,height=1,class="dropdown",name="mega",items=g_impex,value="import signs"}, {x=11,y=4,width=1,height=1,class="checkbox",name="keep",label="keep line",value=true,}, {x=9,y=5,width=3,height=1,class="checkbox",name="restr",label="style restriction (lyrics)",value=false,}, {x=9,y=6,width=3,height=1,class="edit",name="rest"},

-- chapters {x=9,y=7,width=1,height=1,class="label",label="Chapters"}, {x=10,y=7,width=2,height=1,class="checkbox",name="intro",label="autogenerate \"Intro\"",value=true,}, {x=9,y=8,width=2,height=1,class="label",label="chapter marker:"},

{x=11,y=8,width=1,height=1,class="dropdown",name="marker",items={"actor","effect","comment"},value="actor"}, {x=9,y=9,width=2,height=1,class="label",label="chapter name:"}, {x=11,y=9,width=1,height=1,class="dropdown",name="nam",items={"comment","effect"},value="comment"}, {x=9,y=10,width=2,height=1,class="label",label="filename from:"}, {x=11,y=10,width=1,height=1,class="dropdown",name="sav",items={"script","video"},value="script"}, {x=9,y=11,width=2,height=1,class="checkbox",name="chmark",label="chapter mark:",value=false,hint="just sets the marker. no xml."}, {x=11,y=11,width=1,height=1,class="dropdown",name="chap",items={"Intro","OP","Part A","Part B","Part C","ED","Preview"},value="OP"}, {x=9,y=12,width=3,height=1,class="edit",name="lang"},

-- numbers {x=9,y=13,width=2,height=1,class="label",label="Numbers"}, {x=9,y=14,width=2,height=1,class="dropdown",name="modzero",items={"number lines","add to marker"},value="number lines"}, {x=11,y=14,width=1,height=1,class="dropdown",name="zeros",items={"1","01","001","0001"},value=zerozz},

{x=9,y=15,width=2,height=1,class="dropdown",name="field",items={"actor","effect","layer","style","text"},value=fld} ,

-- stuff {x=0,y=15,width=1,height=1,class="label",label="Stuff "}, {x=1,y=15,width=2,height=1,class="dropdown",name="stuff",items=g_stuff,value=dropstuff}, --dropstuff {x=3,y=15,width=1,height=1,class="dropdown",name="regex",items={"lua patterns","perl regexp"},value="perl regexp"}, {x=4,y=15,width=1,height=1,class="checkbox",name="log",label="log",value=lok,hint="replacers"}, {x=8,y=15,width=1,height=1,class="label",label="Marker:"},

-- textboxes {x=0,y=0,width=9,height=15,class="textbox",name="dat",value=data}, {x=9,y=1,width=3,height=1,class="label",label=" Selected Lines: "..#sel},

-- help {x=9,y=0,width=3,height=1,class="dropdown",name="help", items={"--- Help menu ---","Import/Export","Update Lyrics","Do Stuff","Numbers","Chapters"},value="--- Help menu ---"}, {x=9,y=17,width=3,height=1,class="label",label=" Significance version: "..script_version}, } loadconfig() repeat if pressed=="Help" then aegisub.progress.title("Loading Help") aegisub.progress.task("RTFM") if res.help=="Import/Export" then help=help_i end if res.help=="Update Lyrics" then help=help_u end if res.help=="Do Stuff" then help=help_d end if res.help=="Numbers" then help=help_n end if res.help=="Chapters" then help=help_c end if res.help=="--- Help menu ---" then help="Choose something from the menu, dumbass -->" end for key,val in ipairs(unconfig) do if val.name=="dat" then val.value=help end end end if pressed=="Info" then aegisub.progress.title("Gathering Info") aegisub.progress.task("...") info(subs,sel,act) for key,val in ipairs(unconfig) do if val.name=="dat" then val.value=infodump end end end pressed,res=ADD(unconfig, {"Import/Export","Do Stuff","Numbers","Chapters","Repeat Last","Info","Help","Save Config","Cancel"},{ok='Import/Export',cancel='Cancel'}) until pressed~="Help" and pressed~="Info" if pressed=="Cancel" then ak() end lastimp=true lastuff=res.stuff lastlog=res.log lastzeros=res.zeros lastfield=res.field if pressed=="Repeat Last" then if not lastres then ak() end pressed=lastP res=lastres end ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame progress("Doing Stuff") aegisub.progress.task(msge) sub1=res.rep1 sub2=res.rep2 sub3=res.rep3 zer=res.zeros if pressed=="Import/Export" then important(subs,sel,act) end if pressed=="Numbers" then numbers(subs,sel) end if pressed=="Chapters" then chopters(subs,sel) end if pressed=="Do Stuff" then if res.stuff=="jump to next" then sel=nextsel(subs,sel) elseif res.stuff=="convert framerate" then framerate(subs) elseif res.stuff=="alpha shift" then alfashift(subs,sel) elseif res.stuff=="merge tags" then sel=merge(subs,sel) elseif res.stuff=="honorificslaughterhouse" then honorifix(subs,sel) else sel=stuff(subs,sel,act) end end lastP=pressed lastres=res if pressed=="Save Config" then saveconfig() end

aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(significance) else aegisub.register_macro(script_name,script_description,significance) end -- An enhanced version of TPP + Shift Times. -- It can not only add, but also cut leads, prevent adding leads on keyframes, fix overlaps, and much more. -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#shiftcut script_name="ShiftCut" script_description="Time Machine." script_author="unanimated" script_version="2.9" script_namespace="ua.ShiftCut" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.9.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end function style(subs) styles={"All","All Default","Default+Alt","------"} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i].name) end if subs[i].class=="dialogue" then break end end return styles end function runcheck() run=0 if res.stail=="All" then run=1 end if res.stail=="All Default" and line.style:match("Defa") then run=1 end if res.stail=="Default+Alt" and line.style:match("Defa") then run=1 end if res.stail=="Default+Alt" and line.style:match("Alt") then run=1 end if res.stail==line.style then run=1 end if line.style==res.plustyle then run=1 end return run end

-- Lead in -- function cutin(subs,sel) for z,i in ipairs(sel) do line=subs[i] inn=res.inn start=line.start_time endt=line.end_time prevline=subs[i-1] if prevline.class=="dialogue" then prevend=prevline.end_time end run=runcheck()

kfr=1 if res.holdkf then startf=ms2fr(start) for k,kf in ipairs(keyframes) do if kf==startf then kfr=0 break end end end

if run==1 and kfr==1 then if res.cutin then -- cut if (start+inn)

-- Lead out -- function cutout(subs,sel) for z,i in ipairs(sel) do line=subs[i] ut=res.utt start=line.start_time endt=line.end_time if i<#subs then nextline=subs[i+1] nextart=nextline.start_time end run=runcheck()

kfr=1 if res.holdkf then endf=ms2fr(endt) for k,kf in ipairs(keyframes) do if kf==endf then kfr=0 break end end end

if run==1 and kfr==1 then if res.cutout then -- cut if (endt-ut)>start then endt=(endt-ut) else endt=start end else endt=(endt+ut) -- add if res.preventcut and i<#subs and endt>nextart and start

-- Shifting -- function shiift(subs,sel) for z,i in ipairs(sel) do line=subs[i] shift=res.shifft start=line.start_time endt=line.end_time run=runcheck()

if run==1 then if not res.shit then start=(start-shift) endt=(endt-shift) else start=(start+shift) endt=(endt+shift) end start=fr2ms(ms2fr(start)) or start endt=fr2ms(ms2fr(endt)) or endt line.start_time=start line.end_time=endt subs[i]=line end end end function endshift(subs,sel,act) vfcheck() FR=ms2fr(subs[act].end_time) dist=vframe-FR+1 for z,i in ipairs(sel) do line=subs[i] SF=ms2fr(line.start_time) line.start_time=fr2ms(SF+dist) EF=ms2fr(line.end_time) line.end_time=fr2ms(EF+dist) subs[i]=line end end function vfcheck() if aegisub.project_properties==nil then t_error("Current frame unknown.\nProbably your Aegisub is too old.\nMinimum required: r8374.",true) end vframe=aegisub.project_properties().video_position if vframe==nil or fr2ms(1)==nil then t_error("Current frame unknown. Probably no video loaded.",true) end end

-- Linking -- function linklines(subs,sel) marker=0 linx=0 overl=0 for z,i in ipairs(sel) do line=subs[i] lnk=res.link start=line.start_time endt=line.end_time s1=start e1=endt if i<#subs then nextline=subs[i+1] nextart=nextline.start_time nextf=ms2fr(nextart) end if marker==1 then start=start-diff2 end -- link line 2 if markover==1 then start=start+diffo end -- overlap line 2 marker=0 markover=0 run=runcheck() if res.holdkf then endf=ms2fr(endt) for k,kf in ipairs(keyframes) do if kf==endf or kf==nextf then run=0 break end end end

if run==1 then -- linking if lnk>0 and nextart>endt and nextart-endt

-- overlaps if res.over and endt>nextart and endt-nextart

-- Snapping -- function keyframesnap(subs,sel) snapd=0 if res.pres then kfsb,kfeb,kfsa,kfea=res.preset:match("(%d+),(%d+),(%d+),(%d+)") kfsb=tonumber(kfsb) kfeb=tonumber(kfeb) kfsa=tonumber(kfsa) kfea=tonumber(kfea) else kfsb=res.sb kfeb=res.eb kfsa=res.sa kfea=res.ea end for z,i in ipairs(sel) do aegisub.progress.title(string.format("Snapping Line %d/%d",z,#sel)) line=subs[i] run=runcheck() if run==1 then -- snapping to keyframes

start=line.start_time -- start time endt=line.end_time -- end time startemp=start endtemp=endt if z~=#sel then nextline=subs[i+1] nextart=nextline.start_time end if z~=1 then prevline=subs[i-1] prevend=prevline.end_time else prevend=0 end

startf=ms2fr(start) -- startframe endf=ms2fr(endt) -- endframe fr1=endf-startf

diff=250 diffe=250 startkf=keyframes[1] endkf=keyframes[#keyframes]

-- check for nearby keyframes for k,kf in ipairs(keyframes) do

-- startframe if kf>=startf-kfsa and kf<=startf+kfsb then tdiff=math.abs(startf-kf) if tdiff<=diff then diff=tdiff startkf=kf end startemp=fr2ms(startkf)

stopstart=0 if res.prevent and z~=1 and startemp=prevend then stopstart=1 end if stopstart==0 then start=startemp end end

-- endframe if kf>=endf-kfea and kf<=endf+kfeb then tdiff=math.abs(endf-kf) if tdiff

stopend=0 if res.prevent and z~=#sel and endtemp>nextart and endkf-endf>kfsb then stopend=1 end if stopend==0 then endt=endtemp end end end

-- CPS check startok=true endok=true if res.cps>0 then char=line.text:gsub("{[^}]-}","") :gsub("\\[Nn]","*") :gsub("%s?%*+%s?"," ") :gsub("[%s%p]","") linelen=char:len() startf2=ms2fr(start) endf2=ms2fr(endt) dura1=(line.end_time-start)/1000 cps1=math.ceil(linelen/dura1) dura2=(endt-start)/1000 if startf2-startf>3 and line.start_time>=prevend and cps1>res.cps then startok=false line.effect=line.effect.."[cps1]" dura2=(endt-line.start_time)/1000 end cps2=math.ceil(linelen/dura2) if endf-endf2>3 and cps2>res.cps then endok=false line.effect=line.effect.."[cps2]" end end

if startok then line.start_time=start end if endok and endt-start>450 then line.end_time=endt end startf2=ms2fr(line.start_time) endf2=ms2fr(line.end_time) if res.mark then if startf2~=startf or endf2~=endf then line.effect=line.effect.."[snapped]" end end if res.info then if startf2~=startf or endf2~=endf then snapd=snapd+1 end end subs[i]=line end end end function selectall(subs,sel) sel={} for i=1,#subs do if subs[i].class=="dialogue" then table.insert(sel,i) end end return sel end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end

-- Config Stuff -- function saveconfig() scconf="ShiftCut Config:\n\n" for k,v in ipairs(GUI) do if v.class=="floatedit" or v.class=="dropdown" then scconf=scconf..v.name..":"..res[v.name].."\n" end if v.class=="checkbox" and v.name~="save" then scconf=scconf..v.name..":"..tf(res[v.name]).."\n" end if v.name=="preset" then prsts="keyframe presets:" for v=1,#v.items do prsts=prsts..v.items[v]..":" end scconf=scconf..prsts.."\n" end end shiftkonfig=aegisub.decode_path("?user").."\\shiftcut.conf" file=io.open(shiftkonfig,"w") file:write(scconf) file:close() press,rez=ADD({{class="label",label="Config saved to:\n"..shiftkonfig}},{"OK","Add/Delete kf preset","Restore Defaults"},{close='OK'}) if press=="Add/Delete kf preset" then ite=scconf:match("keyframe presets:(.-)\n") pressets="" for it in ite:gmatch("[^:]+") do pressets=pressets..it.."\n" end repeat if press=="Reset" then pressets="1,1,1,1\n2,2,2,2\n6,6,6,10\n6,6,8,12\n6,6,10,12\n6,10,8,12\n8,8,8,12\n0,0,0,10\n7,12,10,13" end press,rez=ADD({{class="textbox",x=0,y=0,width=12,height=12,name="addpres",value=pressets}}, {"Save","Reset"},{ok='Save'}) until press~="Reset" newpres=rez.addpres.."\n" newpres=newpres:gsub("\n",":") :gsub("::",":") scconf=scconf:gsub(ite,newpres) file=io.open(shiftkonfig,"w") file:write(scconf) file:close() ADD({{class="label",label="Saved"}},{"OK"},{close='OK'}) end if press=="Restore Defaults" then file=io.open(shiftkonfig,"w") file:write("") file:close() ADD({{class="label",label="Defaults restored"}},{"OK"},{close='OK'}) end end function loadconfig() sconfig=aegisub.decode_path("?user").."\\shiftcut.conf" file=io.open(sconfig) if file~=nil then konf=file:read("*all") io.close(file) for k,v in ipairs(GUI) do if v.class=="floatedit" or v.class=="checkbox" or v.class=="dropdown" then if konf:match(v.name) then v.value=detf(konf:match(v.name..":(.-)\n")) end end if v.name=="preset" then ite=konf:match("keyframe presets:(.-)\n") if ite~=nil then v.items={} for it in ite:gmatch("[^:]+") do table.insert(v.items,it) end end end end end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function konfig(subs,sel,act) BIAS={"0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"} kf_snap_presets={"1,1,1,1","2,2,2,2","6,6,6,10","6,6,8,12","6,6,10,12","6,10,8,12","8,8,8,12","0,0,0,10","7,12,10,13"} style(subs) GUI={ {x=0,y=0,width=2,class="label",label="ShiftCut v"..script_version}, {x=2,y=0,width=2,class="dropdown",name="slct",items={"Apply to selected","Apply to all lines"},value="Apply to selected"},

{x=0,y=1,width=2,class="label",label="Styles to aply to:"}, {x=2,y=1,width=2,class="dropdown",name="stail",items=styles,value="All Default"}, {x=4,y=1,width=3,class="edit",name="plustyle",value="",hint="Additional style"},

{x=4,y=0,width=4,class="checkbox",name="info",label="Info (link/snap)"}, {x=8,y=0,width=2,class="checkbox",name="mark",label="Mark changed lines",hint="linking/snapping - marks changed lines in effect"},

-- shift {x=0,y=2,class="label",label="SHIFT < backward"}, {x=2,y=2,width=2,class="floatedit",name="shifft",value=0}, {x=4,y=2,class="label",label="ms"}, {x=5,y=2,class="checkbox",name="shit",label=">",hint="Shift forward"}, {x=6,y=2,class="checkbox",name="endshit",label="by end",hint="Shift sel. lines by end of active line\nto current video frame"},

-- add/cut {x=0,y=3,class="checkbox",name="IN",label="Add lead in"}, {x=2,y=3,width=2,class="floatedit",name="inn",value=0}, {x=4,y=3,class="label",label="ms"}, {x=5,y=3,width=2,class="checkbox",name="cutin",label="Cut lead in"},

{x=0,y=4,class="checkbox",name="OUT",label="Add lead out"}, {x=2,y=4,width=2,class="floatedit",name="utt",value=0}, {x=4,y=4,class="label",label="ms"}, {x=5,y=4,width=2,class="checkbox",name="cutout",label="Cut lead out"},

{x=0,y=5,width=3,class="checkbox",name="preventcut", label="prevent overlaps from adding leads",value=true}, {x=3,y=5,width=4,class="checkbox",name="holdkf",label="don't add leads on keyframes"},

-- linking {x=0,y=6,width=2,class="label",label="Line linking: Max gap:"}, {x=2,y=6,width=2,class="floatedit",name="link",value=400,min=0}, {x=4,y=6,class="label",label="ms "}, {x=5,y=6,class="label",label="Bias: "}, {x=6,y=6,class="dropdown",name="bias",items=BIAS,value="0.8",hint="higher number=closer to 2nd line"},

-- overlaps {x=0,y=7,width=2,class="checkbox",name="over",label="Fix overlaps up to:", hint="This is part of line linking.\nIf you want only overlaps, set linking gap to 0."}, {x=2,y=7,width=2,class="floatedit",name="overlap",value=500,min=0 }, {x=4,y=7,class="label",label="ms "}, {x=5,y=7,class="label",label="Bias: "}, {x=6,y=7,class="dropdown",name="bios",items=BIAS,value="0.5",hint="higher number=closer to 2nd line"},

-- keyframes {x=8,y=1,class="label",label="Keyframes"}, {x=9,y=1,class="checkbox",name="prevent",label="Prevent overlaps",value=true},

{x=8,y=2,class="label",label="Starts before:"}, {x=8,y=3,class="label",label="Ends before:"}, {x=8,y=4,class="label",label="Starts after:"}, {x=8,y=5,class="label",label="Ends after:"},

{x=9,y=2,class="floatedit",name="sb",value=0,min=0,max=250,hint="frames, not ms"}, {x=9,y=3,class="floatedit",name="eb",value=0,min=0,max=250,hint="frames, not ms"}, {x=9,y=4,class="floatedit",name="sa",value=0,min=0,max=250,hint="frames, not ms"}, {x=9,y=5,class="floatedit",name="ea",value=0,min=0,max=250,hint="frames, not ms"},

{x=8,y=6,class="checkbox",name="pres",label="Preset:",value=true}, {x=9,y=6,class="dropdown",name="preset",items=kf_snap_presets,value="6,10,8,12"},

{x=8,y=7,class="label",label="Max CPS:"}, {x=9,y=7,class="floatedit",name="cps",value=24,min=0,hint="don't snap if CPS would exceed the limit (0=disable)"}, } loadconfig() P,res=ADD(GUI,{"Lead in/out","Link lines","Shift times","Keyframe snap","All","Save config","Cancel"},{cancel='Cancel'}) if res.slct=="Apply to all lines" then sel=selectall(subs,sel) end keyframes=aegisub.keyframes() ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame

if P=="Lead in/out" or P=="All" then if res.IN or res.cutin then cutin(subs,sel) end if res.OUT or res.cutout then cutout(subs,sel) end end if P=="Shift times" then if res.endshit then endshift(subs,sel,act) else shiift(subs,sel) end end if P=="Link lines" or P=="All" then linklines(subs,sel) end if P=="Keyframe snap" or P=="All" then keyframesnap(subs,sel) end

if P=="Cancel" then ak() end if res.info then if P=="Link lines" or P=="All" then alog("\n Linked lines: "..linx) end if P=="Link lines" and res.over or P=="All" and res.over then alog("\n Overlaps fixed: "..overl) end if P=="Keyframe snap" or P=="All" then alog("\n Lines snapped to keyframes: "..snapd) end end if P=="Save config" then saveconfig() end return sel end function shiftcut(subs,sel,act) ADD=aegisub.dialog.display ak=aegisub.cancel alog=aegisub.log konfig(subs,sel,act) aegisub.set_undo_point(script_name) return sel end function shiftEF(subs,sel) SL=3 shiftlink(subs,sel) end function shiftEB(subs,sel) SL=-3 shiftlink(subs,sel) end function shiftlink(subs,sel) ak=aegisub.cancel ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame for z,i in ipairs(sel) do line=subs[i] endt=line.end_time EF=ms2fr(endt) if i<#subs then line2=subs[i+1] start2=line2.start_time SF=ms2fr(start2) end if SF and SF==EF then SF=SF+SL line2.start_time=fr2ms(SF) subs[i+1]=line2 end EF=EF+SL line.end_time=fr2ms(EF) subs[i]=line SF=nil end end function shiftSF(subs,sel) SS=3 shiftstart(subs,sel) end function shiftSB(subs,sel) SS=-3 shiftstart(subs,sel) end function shiftstart(subs,sel) ak=aegisub.cancel ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame for z,i in ipairs(sel) do line=subs[i] start=line.start_time SF=ms2fr(start) if subs[i-1].class=="dialogue" then line0=subs[i-1] end0=line0.end_time EF=ms2fr(end0) end if EF and SF==EF then EF=EF+SS line0.end_time=fr2ms(EF) subs[i-1]=line0 end SF=SF+SS line.start_time=fr2ms(SF) subs[i]=line EF=nil end end if haveDepCtrl then depRec:registerMacros({ {"ShiftCut/ShiftCut",script_description,shiftcut}, {"ShiftCut/Shift End Link Forward","Shift end linking by 3 frames right",shiftEF}, {"ShiftCut/Shift End Link Backward","Shift end linking by 3 frames left",shiftEB}, {"ShiftCut/Shift Start Link Forward","Shift start linking by 3 frames right",shiftSF}, {"ShiftCut/Shift Start Link Backward","Shift start linking by 3 frames left",shiftSB}, },false) else aegisub.register_macro("ShiftCut/ShiftCut",script_description,shiftcut) aegisub.register_macro("ShiftCut/Shift End Link Forward","Shift end linking by 3 frames right",shiftEF) aegisub.register_macro("ShiftCut/Shift End Link Backward","Shift end linking by 3 frames left",shiftEB) aegisub.register_macro("ShiftCut/Shift Start Link Forward","Shift start linking by 3 frames right",shiftSF) aegisub.register_macro("ShiftCut/Shift Start Link Backward","Shift start linking by 3 frames left",shiftSB) end -- Times a sign with {TS 3:24} to 3:24-3:25. Can convert and use a few other formats, like {3:24}, {TS,3:24}, {3,24} etc. -- supported timecodes: {TS 1:23}; {TS 1:23 words}; {TS words 1:23}; {TS,1:23}; {1:23}; {1;23}; {1,23}; {1.23}; [1:23] and variations -- Manuals for all my scripts: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm script_name="Time Signs" script_description="Rough-times signs from TS timecodes" script_author="unanimated" script_version="2.8" script_namespace="ua.TimeSigns" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.8.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end function signtime(subs,sel) for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text -- format timecodes text=text :gsub("({.-})({TS.-})","%2%1") :gsub("(%d+):(%d%d):(%d%d)", function(a,b,c) return a*60+b..":"..c end) -- hours :gsub("^(%d%d%d%d)%s%s*","{TS %1}") -- ^1234 text :gsub("(%d%d)ish","%1") -- 1:23ish :gsub("^([%d%s:,%-]+)","{%1}") -- ^1:23, 2:34, 4:56 :gsub("^(%d+:%d%d)%s%-%s","{TS %1}") -- ^12?:34 - :gsub("^(%d+:%d%d%s*)","{TS %1}") -- ^12?:34 :gsub("^{(%d+:%d%d)","{TS %1") -- ^{12?:34 :gsub("^%[(%d+:%d%d)%]:?%s*","{TS %1}") -- ^[12?:34]:? :gsub("{TS[%s%p]+(%d)","{TS %1") -- {TS ~12:34 :gsub("({[^}]-)(%d+)[%;%.,]?(%d%d)([^:%d][^}]-})","%1%2:%3%4") -- {1;23 / 1.23 / 1,23 123} :gsub("{TS%s([^%d\\}]+)%s(%d+:%d%d)","{TS %2 %1") -- {TS comment 12:34} :gsub(":%s?}","}") -- {TS 12:34: } :gsub("|","\\N") tc=text:match("^{[^}]-}") or "" tc=tc:gsub("(%d+)(%d%d)([^:])","%1:%2%3") text=text:gsub("^{[^}]-}%s*",tc) if res.blur then text=text:gsub("\\blur[%d%.]+",""):gsub("{}",""):gsub("^","{\\blur"..res.bl.."}") end line.text=text

tstags=text:match("{TS[^}]-}") or ""

times={} -- collect times if there are more for tag in tstags:gmatch("%d+:%d+") do table.insert(times,tag) end for t=#times,1,-1 do tim=times[t] -- convert to start time tstid1,tstid2=tim:match("(%d+):(%d%d)") if tstid1 then tid=(tstid1*60000+tstid2*1000-500) end -- shifting times if tid then if res.shift then tid=tid+res.secs*1000 end -- set start and end time [500ms before and after the timecode] line.start_time=tid line.end_time=(tid+1000) end

-- snapping to keyframes if res.snap then start=line.start_time endt=line.end_time startf=ms2fr(start) endf=ms2fr(endt) diff=250 diffe=250 startkf=keyframes[1] endkf=keyframes[#keyframes]

-- check for nearby keyframes for k,kf in ipairs(keyframes) do -- startframe snap up to 24 frames back [scroll down to change default] and 5 frames forward if kf>=startf-res.kfs and kf=endf-10 and kf<=endf+res.kfe then tdiff=math.abs(endf-kf) if tdiff1 then table.insert(sel,sel[#sel]+1) end end if #times>0 then subs.delete(i) end end if res.copy then taim=0 tame=0 for z,i in ipairs(sel) do l=subs[i] if l.start_time==0 then l.start_time=taim l.end_time=tame end taim=l.start_time tame=l.end_time subs[i]=l end end return sel end

-- Config Stuff -- function saveconfig() savecfg="Time Signs Settings\n\n" for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="intedit" or val.class=="checkbox" and val.name~="save" then savecfg=savecfg..val.name..":"..tf(res[val.name]).."\n" end end file=io.open(cfgpath,"w") file:write(savecfg) file:close() ADD({{class="label",label="Config saved to:\n"..cfgpath}},{"OK"},{close='OK'}) end function loadconfig() cfgpath=aegisub.decode_path("?user").."\\timesigns.conf" file=io.open(cfgpath) if file~=nil then konf=file:read("*all") file:close() for key,val in ipairs(GUI) do if val.class=="floatedit" or val.class=="checkbox" or val.class=="intedit" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end function timesigns(subs,sel) ADD=aegisub.dialog.display GUI={ {x=0,y=0,width=4,class="label",label="Check this if all your timecodes are too late or early:"}, {x=0,y=1,class="checkbox",name="shift",label="Shift timecodes by "}, {x=1,y=1,width=2,class="floatedit",name="secs",value=-10,hint="Negative=backward / positive=forward",step=0.5}, {x=3,y=1,class="label",label=" sec."}, {x=0,y=2,width=4,class="checkbox",name="copy",label="For lines without timecodes, copy from the previous line"}, {x=0,y=3,width=4,class="checkbox",name="nots",label="Automatically remove {TS ...} comments"}, {x=0,y=4,width=2,class="checkbox",name="blur",label="Automatically add blur:"}, {x=2,y=4,width=2,class="floatedit",name="bl",value="0.6"}, {x=0,y=5,width=2,class="checkbox",name="snap",label="Snapping to keyframes:",value=true}, {x=0,y=6,width=2,class="label",label="Frames to search back:"}, {x=0,y=7,width=2,class="label",label="Frames to search forward:"}, {x=2,y=6,width=2,class="intedit",name="kfs",value="24",step=1,min=1,max=250}, {x=2,y=7,width=2,class="intedit",name="kfe",value="24",step=1,min=1,max=250}, {x=0,y=8,width=2,class="checkbox",name="save",label="Save current settings"}, {x=2,y=8,width=2,class="label",label=" [Time Signs v"..script_version.."]"}, } loadconfig() buttons={"No more suffering with SHAFT signs!","Exit"} P,res=ADD(GUI,buttons,{ok='No more suffering with SHAFT signs!',cancel='Exit'}) if P=="Exit" then aegisub.cancel() end ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame keyframes=aegisub.keyframes() if res.save then saveconfig() end if P=="No more suffering with SHAFT signs!" then sel=signtime(subs,sel) end aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacros({{script_name,script_description,timesigns}},false) else aegisub.register_macro(script_name,script_description,timesigns) end script_name="Change Case" script_description="Capitalises text or makes it lowercase / uppercase" script_author="unanimated" script_version="3.0" script_namespace="ua.ChangeCase" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' unicode=require'aegisub.unicode' function case(subs,sel) for z,i in ipairs(sel) do line=subs[i] t=line.text if P=="lowercase" then t=lowercase(t) end if P=="UPPERCASE" then t=uppercase(t) end if P=="Lines" then t=capitalines(t) end if P=="Sentences" then if res.mod then res.mod=false t=lowercase(t) res.mod=true end t=sentences(t) end if P=="Words" then if not res.mod then t=lowercase(t) end t=capitalise(t) end line.text=t subs[i]=line end end function lowercase(t) t=t :gsub("\\[Nnh]","{%1}") :gsub("^([^{]*)",function(l) if res.mod then l=re.sub(l,[[\b(\u\u+'?\u*)]],function(u) return ulower(u) end) return l else return ulower(l) end end) :gsub("}([^{]*)",function(l) if res.mod then l=re.sub(l,[[\b(\u\u+'?\u*)]],function(u) return ulower(u) end) return "}"..l else return "}"..ulower(l) end end) :gsub("{(\\[Nnh])}","%1") return t end function uppercase(t) t=t :gsub("\\[Nnh]","{%1}") :gsub("^([^{]*)",function(u) return uupper(u) end) :gsub("}([^{]*)",function(u) return "}"..uupper(u) end) :gsub("{(\\[Nnh])}","%1") return t end function capitalines(t) t=re.sub(t,[[^(["']?\l)]],function(l) return uupper(l) end) t=re.sub(t,[[^\{[^}]*\}(["']?\l)]],function(l) return uupper(l) end) if not res.mod then t=t:gsub(" i([' %?!%.,])"," I%1"):gsub("\\Ni([' ])","\\NI%1") end return t end function sentences(t) somewords={"English","Japanese","American","British","German","French","Spanish","Monday","Tuesday","Wednes day","Thursday","Friday","Saturday","Sunday","January","February","April","June","July","August","September","Oct ober","November","December"} hnrfx={"%-san","%-kun","%-chan","%-sama","%-dono","%-se[nm]pai","%-on%a+an"} t=re.sub(t,[[^(["']?\l)]],function(l) return uupper(l) end) t=re.sub(t,[[^\{[^}]*\}(["']?\l)]],function(l) return uupper(l) end) t=re.sub(t,[[[\.\?!](\s|\s\\N|\\N)["']?(\l)]],function(l) return uupper(l) end) t=t :gsub(" i([' %?!%.,])"," I%1") :gsub("\\Ni([' ])","\\NI%1") :gsub(" m(arch %d)"," M%1") :gsub(" a(pril %d)"," A%1") for l=1,#somewords do t=t:gsub(somewords[l]:lower(),somewords[l]) end for h=1,#hnrfx do t=t:gsub("([ %p]%l)(%l*"..hnrfx[h]..")",function(h,f) return h:upper()..f end) t=t:gsub("(\\N%l)(%l*"..hnrfx[h]..")",function(h,f) return h:upper()..f end) end t=re.sub(t,"\\b(of|in|from|\\d+st|\\d+nd|\\d+rd|\\d+th) m(arch|ay)\\b","\\1 M\\2") t=re.sub(t,"\\bm(r|rs|s)\\.","M\\1.") t=re.sub(t,"\\bdr\\.","Dr.") return t end function capitalise(txt) word={"A","About","Above","Across","After","Against","Along","Among","Amongst","An","And","Around","As","A t","Before","Behind","Below","Beneath","Beside","Between","Beyond","But","By","Despite","During","Except","For", "From","In","Inside","Into","Near","Nor","Of","On","Onto","Or","Over","Per","Sans","Since","Than","The","Through" ,"Throughout","Till","To","Toward","Towards","Under","Underneath","Unlike","Until","Unto","Upon","Versus","Via" ,"With","Within","Without","According to","Ahead of","Apart from","Aside from","Because of","Inside of","Instead of","Next to","Owing to","Prior to","Rather than","Regardless of","Such as","Thanks to","Up to","and Yet"} onore={"%-San","%-Kun","%-Chan","%-Sama","%-Dono","%-Se[nm]pai","%-On%a+an"} nokom={"^( ?)([^{]*)","(})([^{]*)"} for n=1,2 do txt=txt:gsub(nokom[n],function(no_t,t) t=t:gsub("\\[Nnh]","{%1}") t=re.sub(t,[[\b\l]],function(l) return uupper(l) end) t=re.sub(t,[[[I\l]'(\u)]],function(l) return ulower(l) end)

for r=1,#word do w=word[r] t=t :gsub("^ "..w.." "," "..w:lower().." ") :gsub("([^%.:%?!]) "..w.." ","%1 "..w:lower().." ") :gsub("([^%.:%?!]) (%b{})"..w.." ","%1 %2"..w:lower().." ") :gsub("([^%.:%?!]) (%*Large_break%* ?)"..w.." ","%1 %2"..w:lower().." ") end

-- Roman numbers (this may mismatch some legit words - sometimes there just are 2 options and it's a guess) t=t :gsub("$","#") :gsub("(%s?)([IVXLCDM])([ivxlcdm]+)([%s%p#])",function(s,r,m,e) return s..r..m:upper()..e end) :gsub("([DLM])ID","%1id") :gsub("DIM","Dim") :gsub("MIX","Mix") :gsub("Ok([%s%p#])","OK%1") for h=1,#onore do t=t:gsub(onore[h].."([%s%p#])",onore[h]:lower().."%1") end t=t :gsub("#$","") :gsub("{(\\[Nnh])}","%1") return no_t..t end) end return txt end ulower=unicode.to_lower_case uupper=unicode.to_upper_case function logg(m) m=m or "nil" aegisub.log("\n "..m) end function capital(subs,sel) GUI={ {x=1,y=0,class="label",label="Words - Capitalise Words Like in Titles"}, {x=1,y=1,class="label",label=" Lines - Capitalise first word in selected lines"}, {x=1,y=2,class="label",label=" Sentences - Capitalise first word in each sentence"}, {x=1,y=3,class="label",label=" Lowercase - make text in selected lines lowercase"}, {x=1,y=4,class="label",label=" Uppercase - MAKE TEXT IN SELECTED LINES UPPERCASE"}, {x=2,y=5,class="label",label=script_name.." v "..script_version}, {x=1,y=5,class="checkbox",name="mod",label="mod",hint="Words - leave uppercase words\nLines - don't capitalize 'i'\nSentences - run lowercase first\nlowercase - only for uppercase words"}, }

P,res=aegisub.dialog.display(GUI,{"Words","Lines","Sentences","lowercase","UPPERCASE","Cancel"},{ok='Words',c lose='Cancel'}) if P=="Cancel" then aegisub.cancel() end case(subs,sel) aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(capital) else aegisub.register_macro(script_name,script_description,capital) end -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#necroscopy

-- OPTIONS (true/false) -- copy_style=true -- "copy style with tags" checked in the gui autogradient_clip2fax=true -- automatically gradient \fax for "fax from clip" with 4-point clip -- END OF OPTIONS -- script_name="NecrosCopy" script_description="Copy and Fax Things" script_author="unanimated" script_version="3.0" script_namespace="ua.NecrosCopy" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="3.0.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' function fucks(subs,sel) if res.right then res.fax=0-res.fax end for z,i in ipairs(sel) do l=subs[i] t=l.text

if not res.clax then t=t:gsub("^({[^}]-\\fax)[%d%.%-]+","%1"..res.fax) if not t:match("^{[^}]-\\fax") then t="{\\fax"..res.fax.."}"..t t=t:gsub("^({\\[^}]-)}{\\","%1\\") end else if not t:match("\\clip") then t_error("Missing \\clip.",true) end cx1,cy1,cx2,cy2,cx3,cy3,cx4,cy4=t:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+) ([%d%-]+)") if cx1==nil then cx1,cy1,cx2,cy2=t:match("\\clip%(m ([%d%-]+) ([%d%-]+) l ([%d%-]+) ([%d%-]+)") end sr=stylechk(subs,l.style) nontra=t:gsub("\\t%b()","") rota=nontra:match("\\frz([%d%.%-]+)") or sr.angle scx=nontra:match("\\fscx([%d%.]+)") or sr.scale_x scy=nontra:match("\\fscy([%d%.]+)") or sr.scale_y scr=scx/scy ad=cx1-cx2 op=cy1-cy2 tang=(ad/op) ang1=math.deg(math.atan(tang)) ang2=ang1-rota tangf=math.tan(math.rad(ang2))

faks=round(tangf/scr*100)/100 t=addtag("\\fax"..faks,t) if cy4~=nil then tang2=((cx3-cx4)/(cy3-cy4)) ang3=math.deg(math.atan(tang2)) ang4=ang3-rota tangf2=math.tan(math.rad(ang4)) faks2=round(tangf2*100)/100 endcom="" repeat t=t:gsub("({[^}]-})%s*$",function(ec) endcom=ec..endcom return "" end) until not t:match("}$") t=t:gsub("(.)$","{\\fax"..faks2.."}%1")

if autogradient_clip2fax then vis=t:gsub("{[^}]-}","") orig=t:gsub("^{\\[^}]*}","") tg=t:match("^{\\[^}]-}") chars={} ltrz=re.find(vis,".") for l=1,#ltrz do table.insert(chars,ltrz[l].str) end faxdiff=(faks2-faks)/(#chars-1) tt=chars[1] for c=2,#chars do if c==#chars then ast="" else ast="*" end if chars[c]==" " then tt=tt.." " else tt=tt.."{"..ast.."\\fax"..round((faks+faxdiff*(c-1))*100)/100 .."}"..chars[c] end end t=tg..tt if orig:match("{%*?\\") then t=textmod(orig,t) end end t=t..endcom end

t=t:gsub("\\clip%b()","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("(\\fax[%d%.%-]+)([^}]-)(\\fax[%d%.%-]+)","%3%2") :gsub("%**}","}") end l.text=t subs[i]=l end end function copystuff(subs,sel) -- get stuff from line 1 rine=subs[sel[1]] ftags=rine.text:match("^{(\\[^}]-)}") or "" cstext=rine.text:gsub("^{(\\[^}]-)}","") vis=rine.text:gsub("%b{}","") csstyle=rine.style csst=rine.start_time cset=rine.end_time -- detect / save / remove transforms ftra="" if ftags:match("\\t") then for t in ftags:gmatch("\\t%b()") do ftra=ftra..t end ftags=ftags:gsub("\\t%b()","") end rept={"drept"} -- build GUI copyshit={ {x=0,y=0,class="checkbox",name="chks",label="[ Start Time ] "}, {x=0,y=1,class="checkbox",name="chke",label="[ End Time ]"}, {x=1,y=0,class="checkbox",name="css",label="[ Style ] "}, {x=1,y=1,class="checkbox",name="tkst",label="[ Text ]"}, {x=2,y=0,class="label",label="Place * in text below to copy tags there"}, {x=2,y=1,class="checkbox",name="breaks",label="Copy tags after all linebreaks (all lines) ",realname=""}, {x=0,y=2,width=3,class="edit",name="ltxt",value=vis,hint="only works for first selected line"}, } ftw=3 -- regular tags -> GUI for f in ftags:gmatch("\\[^\\]+") do lab=f:gsub("&","&&") if f:match("\\i?clip%(m") then lab=f:match("\\i?clip%(m [%d%.%-]+ [%d%.%-]+ %a [%d%.%-]+ [%d%.%-]+ ").."..." end if f:match("\\move") then lab=f:gsub("%.%d+","") end cb={x=0,y=ftw,width=2,class="checkbox",name="chk"..ftw,label=lab,realname=f} drept=0 for r=1,#rept do if f==rept[r] then drept=1 end end if drept==0 then table.insert(copyshit,cb) ftw=ftw+1 table.insert(rept,f) end end -- transform tags for f in ftra:gmatch("\\t%b()") do cb={x=0,y=ftw,width=2,class="checkbox",name="chk"..ftw,label=f} drept=0 for r=1,#rept do if f==rept[r] then drept=1 end end if drept==0 then table.insert(copyshit,cb) ftw=ftw+1 table.insert(rept,f) end end itw=3 -- inline tags if cstext:match("{[^}]-\\[^Ntrk]") then for f in cstext:gmatch("\\[^tNhrk][^\\}%)]+") do lab=f:gsub("&","&&") if itw==22 then lab="(that's enough...)" f="" end if itw==23 then break end cb={x=2,y=itw,class="checkbox",name="chk2"..itw,label=lab,realname=f} drept=0 for r=1,#rept do if f==rept[r] then drept=1 end end if drept==0 then table.insert(copyshit,cb) itw=itw+1 table.insert(rept,f) end end end repeat if press=="Check All Tags" then for key,val in ipairs(copyshit) do if val.class=="checkbox" and not val.label:match("%[ ") and val.x==0 then val.value=true end end end press,rez=ADD(copyshit,{"Copy","Check All Tags","Paste Saved","[Un]hide","Cancel"},{ok='Copy',close='Cancel'}) until press~="Check All Tags" if press=="Cancel" then ak() end -- save checked tags kopytags="" copytfs="" for key,val in ipairs(copyshit) do if rez[val.name]==true and not val.label:match("%[ ") then if not val.label:match("\\t") then kopytags=kopytags..val.realname else copytfs=copytfs..val.label end end end if press=="Paste Saved" then kopytags=savedkopytags copytfs=savedcopytfs sn=1 csstyle=savedstyle csst=savedt1 cset=savedt2 cstext=savedtext rez.css=savedcss rez.chks=savedchks rez.chke=savedchke rez.tkst=savedtkst elseif press=="Copy" then sn=2 savedkopytags=kopytags savedcopytfs=copytfs savedt1=csst savedt2=cset savedstyle=csstyle savedcss=rez.css savedchks=rez.chks savedchke=rez.chke savedtext=cstext savedtkst=rez.tkst end if rez.ltxt:match"%*" then inline=true sn=1 maxx=1 elseif rez.breaks then inline=true sn=1 maxx=#sel else inline=false maxx=#sel end

-- lines 2+ if press~="[Un]hide" then for z=sn,maxx do i=sel[z] line=subs[i] text=line.text text=text:gsub("\\1c","\\c")

if not text:match("^{\\") then text="{\\stuff}"..text end ctags=text:match(STAG) -- handle existing transforms if ctags:match("\\t") then ctags=trem(ctags) if text:match("^{}") then text=text:gsub("^{}","{\\stuff}") end text=text:gsub(STAG,ctags) trnsfrm=trnsfrm..copytfs elseif copytfs~="" then trnsfrm=copytfs end -- add + clean tags if inline then initags=text:match(STAG) or "" endcom="" repeat ec=text:match("{[^\\}]-}$") text=text:gsub("{[^\\}]-}$","") if ec then endcom=ec..endcom end until ec==nil orig=text if rez.breaks then initags="" text=text:gsub("\\N","\\N{"..kopytags.."}") :gsub("\\N{(\\[^}]-})({\\[^}]-)}","\\N%2%1") else text=rez.ltxt:gsub("%*","{"..kopytags.."}") text=textmod(orig,text) end

text=initags..text..endcom else text=text:gsub("^({\\[^}]-)}","%1"..kopytags.."}") end

text=text:gsub(ATAG,function(tg) return duplikill(tg) end) text=extrakill(text,2) -- add transforms

if trnsfrm then text=text:gsub("^({\\[^}]*)}","%1"..trnsfrm.."}") end trnsfrm=nil text=text:gsub(STAG,function(tags) return cleantr(tags) end) text=text:gsub("\\stuff","") :gsub("{}","")

if rez.css then line.style=csstyle end if rez.chks then line.start_time=csst end if rez.chke then line.end_time=cset end if rez.tkst then text=text:gsub("^({\\[^}]-}).*","%1"..cstext) end line.text=text subs[i]=line end else txt=rine.text -- unhide if kopytags=="" and copytfs=="" then uncom={} wai=0 for com in txt:gmatch("{//([^}]+)}") do table.insert(uncom,{x=0,y=wai,class="checkbox",label=com,name=com}) wai=wai+1 end if #uncom<=1 then txt=txt:gsub("^(.*){//([^}]+})","{\\%2%1") txt=txt:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%2%1}") else pss,rz=ADD(uncom,{"OK","Cancel"},{ok='OK',close='Cancel'}) if pss=="Cancel" then ak() end for key,val in ipairs(uncom) do enam=esc(val.name) if rz[val.name]==true then txt=txt:gsub("^(.*){//("..enam..")}","{\\%2}%1") txt=txt:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%2%1}") end end end -- hide else ekopytags=esc(kopytags) ecopytfs=esc(copytfs) for tg in ekopytags:gmatch("\\[^\\}]+") do txt=txt:gsub(tg,"") end for tg in kopytags:gmatch("\\([^\\}]+)") do txt=txt.."{//"..tg.."}" end for tg in ecopytfs:gmatch("\\t%%%b()") do txt=txt:gsub(tg,"") end for tg in copytfs:gmatch("\\(t%b())") do txt=txt.."{//"..tg.."}" end txt=txt:gsub("{}","") end rine.text=txt subs[sel[1]]=rine end trnsfrm=nil end function copytags(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text if z==1 then tags=text:match(STAG) or "" style=line.style end if z~=1 then text=tags..text:gsub(STAG,"") if res.cpstyle then line.style=style end end line.text=text subs[i]=line end return sel end function copytext(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text if z==1 then tekst=text:gsub(STAG,"") end if z~=1 then tags=text:match(STAG) or "" text=tags..tekst end line.text=text subs[i]=line end return sel end function copyclip(subs,sel) xc=res.xlip yc=res.ylip for z,i in ipairs(sel) do line=subs[i] text=line.text if z==1 then klipstart=text:match("\\i?clip%(([^%)]+)%)") klip=klipstart if not klip then t_error("Error: No clip on line 1.",1) end end if z~=1 then if not text:match("^{\\") then text="{\\klip}"..text end if not text:match("\\i?clip") then text=addtag("\\clip()",text) end

-- calculations if xc~=0 or yc~=0 then factor=z-1 klip=klipstart:gsub("([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)", function(a,b,c,d) return a+xc*factor..","..b+yc*factor..","..c+xc*factor..","..d+yc*factor end) if klipstart:match("m [%d%a%s%-]+") then klip=klipstart:match("m ([%d%a%s%-]+)") klip2=klip:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return a+xc*factor.." "..b+yc*factor end) klip=klip:gsub("%-","%%-") klip=klip:gsub(klip,klip2) klip="m "..klip end end -- set clip text=text:gsub("(\\i?clip)%([^%)]-%)","%1("..klip..")") :gsub("\\klip","") end line.text=text subs[i]=line end return sel end function copycolours(subs,sel) if not res.c1 and not res.c2 and not res.c3 and not res.c4 then ak() end for z,i in ipairs(sel) do line=subs[i] text=line.text sr=stylechk(subs,line.style) text=text:gsub("\\1c","\\c") nontra=text:gsub("\\t%b()","") if z==1 then if res.c1 then col1=nontra:match("\\c(&H%x+&)") or sr.color1:gsub("H%x%x","H") alf1=nontra:match("\\1a(&H%x+&)") or sr.color1:match("H%x%x") end if res.c3 then col3=nontra:match("\\3c(&H%x+&)") or sr.color3:gsub("H%x%x","H") alf3=nontra:match("\\3a(&H%x+&)") or sr.color3:match("H%x%x") end if res.c4 then col4=nontra:match("\\4c(&H%x+&)") or sr.color4:gsub("H%x%x","H") alf4=nontra:match("\\4a(&H%x+&)") or sr.color4:match("H%x%x") end if res.c2 then col2=nontra:match("\\2c(&H%x+&)") or sr.color2:gsub("H%x%x","H") alf2=nontra:match("\\2a(&H%x+&)") or sr.color2:match("H%x%x") end end if z~=1 then if res.c1 then text=addtag3("\\c"..col1,text) end if res.c3 then text=addtag3("\\3c"..col3,text) end if res.c4 then text=addtag3("\\4c"..col4,text) end if res.c2 then text=addtag3("\\2c"..col2,text) end if res.alfa then if res.c1 then text=addtag3("\\1a"..alf1,text) end if res.c3 then text=addtag3("\\3a"..alf3,text) end if res.c4 then text=addtag3("\\4a"..alf4,text) end if res.c2 then text=addtag3("\\2a"..alf2,text) end end end line.text=text subs[i]=line end return sel end function shad3(subs,sel) for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text nontra=text:gsub("\\t%b()","") layer=line.layer text=text:gsub("^({[^}]-)\\shad([%d%.]+)","%1\\xshad%2\\yshad%2") :gsub(STAG,function(tg) return duplikill(tg) end) xshad=tonumber(nontra:match("^{[^}]-\\xshad([%d%.%-]+)")) or 0 ax=math.abs(xshad) yshad=tonumber(nontra:match("^{[^}]-\\yshad([%d%.%-]+)")) or 0 ay=math.abs(yshad) if ax>ay then lay=math.floor(ax) else lay=math.floor(ay) end

text2=text:gsub("^({\\[^}]-)}","%1\\3a&HFF&}") :gsub("\\3a&H%x%x&([^}]-)(\\3a&H%x%x&)","%1%2")

for l=lay,1,-1 do line2=line f=l/lay txt=text2 if l==1 then txt=text end line2.text=txt :gsub("\\xshad([%d%.%-]+)",function(a) xx=tostring(f*a) xx=xx:gsub("([%d%-]+%.%d%d)%d+","%1") return "\\xshad"..xx end) :gsub("\\yshad([%d%.%-]+)",function(a) yy=tostring(f*a) yy=yy:gsub("([%d%-]+%.%d%d)%d+","%1") return "\\yshad"..yy end) line2.layer=layer+(lay-l) subs.insert(i+1,line2) end if xshad~=0 and yshad~=0 then subs.delete(i) else line.text=text subs[i]=line end end return sel end function splitbreak(subs,sel) nsel={} for z,i in ipairs(sel) do table.insert(nsel,i) end for z=#sel,1,-1 do i=sel[z] line=subs[i] text=line.text breakit=true if not text:match("\\N") or res.splitg then if not text:match("\\N") then lab="Selection line "..z.." has no \\N. You can split by spaces or by tags." else lab="Split by spaces or by tags." end if not applytoall then P,rez=ADD({{class="label",label=lab,width=2}, {y=1,class="checkbox",name="remember",label="Apply to all lines"}, {x=1,y=1,class="checkbox",name="num",label="Number lines",value=nmbr}, },{"Spaces","Tags","Skip","Cancel"},{close='Cancel'}) end if P=="Cancel" then ak() end if P=="Spaces" then text=textreplace(text," "," \\N") end if P=="Tags" then text=text:gsub("(.)({\\[^}]-})","%1\\N%2") end if rez.remember then applytoall=P end breakit=false nmbr=rez.num end

-- split by \N if text:match("\\N")then seg=re.split(text,[[\\N *]])

-- positioning with \N (only considers \fs, \fscy, and \an) _,poses=text:gsub("\\N","") poses=poses+1 posY=text:match("\\pos%([%d%.%-]+,([%d%.%-]+)%)") if posY and breakit then sr=stylechk(subs,line.style) fos=text:match("\\fs(%d+)") or sr.fontsize scy=text:match("\\fscy([%d%.]+)") or sr.scale_y siz=fos*scy/100 align=text:match("\\an(%d)") or sr.align align3=3*align postab={} if align3>20 then for p=1,poses do table.insert(postab,posY+(p-1)*siz) end elseif align3<10 then for p=poses,1,-1 do table.insert(postab,posY-(p-1)*siz) end seg[1]=seg[1]:gsub("(\\pos%([%d%.%-]+,)[%d%.%-]+","%1"..postab[1]) else posYm=posY-(poses-1)*siz/2 for p=1,poses do table.insert(postab,posYm+(p-1)*siz) end seg[1]=seg[1]:gsub("(\\pos%([%d%.%-]+,)[%d%.%-]+","%1"..postab[1]) end end

-- pos by spaces/tags (\fs, \fscy, \an) if not breakit then vis=text:gsub("%b{}",""):gsub("\\N","") nontra=text:gsub("\\t%b()","") sr=stylechk(subs,line.style) align=tonumber(text:match("\\an(%d)")) or sr.align if align>3 then repeat align=align-3 until align<4 end scx=nontra:match("^{[^}]-\\fscx([%d%.]+)") or sr.scale_x scx=scx/100 fsize=tonumber(nontra:match("^{[^}]-\\fs(%d+)")) sfs=sr.fontsize if fsize and fsize~=sfs then sfac=fsize/sfs*scx else sfac=scx end w=aegisub.text_extents(sr,vis)*scx wtab={} stab={} for s=1,#seg do seg[s],sp=seg[s]:gsub(" *$","") ws=aegisub.text_extents(sr,seg[s]:gsub("%b{}",""))*scx table.insert(wtab,ws) table.insert(stab,sp) end ws=aegisub.text_extents(sr," ")*scx end

line2=line tags="" for it in seg[1]:gmatch(ATAG) do tags=tags..it end tags=tags:gsub("}{","") count=0 poscount=2 for sg=2,#seg do aftern=seg[sg] t2=tags..aftern if posY and breakit then t2=t2:gsub("(\\pos%([%d%.%-]+,)[%d%.%-]+","%1"..postab[poscount]) else t2=findpos(t2,sg) end poscount=poscount+1 if aftern~="" then count=count+1 t2=t2:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub(ATAG,function(tg) return duplikill(tg) end) tags="" for it in t2:gmatch(ATAG) do tags=tags..it end tags=tags:gsub("}{","") line2.text=t2 if nmbr then if sg<10 then ef="0"..sg else ef=sg end line2.effect=ef end subs.insert(i+count,line2) nsel=shiftsel2(nsel,z,1) end end text=seg[1] if not breakit then text=text:gsub("(\\pos%()([%d%.%-]+)(,[%d%.%-]+)",function(p,x,y) if align==1 then xpos=x end if align==2 then xpos=x-w/2+wtab[1]/2 end if align==3 then xpos=x-w+wtab[1] end return p..round(xpos,1)..y end) end

line.text=text if nmbr then line.effect="01" end subs[i]=line else -- nospace/notag end end sel=nsel applytoall=nil nmbr=nil return sel end function findpos(text,sg) text=text:gsub("(\\pos%()([%d%.%-]+)(,[%d%.%-]+)",function(p,x,y) if sg==2 then if align==2 then x=x-w/2+wtab[1]/2 end if align==3 then x=x-w+wtab[1] end end space=ws if P=="Tags" and stab[sg-1]==1 then space=0 end if align==1 then xpos=round(x+wtab[sg-1]+space,2) end if align==2 then xpos=round(x+wtab[sg-1]/2+wtab[sg]/2+space,2) end if align==3 then xpos=round(x+wtab[sg]+space,2) end return p..round(xpos,1)..y end) return text end

-- reanimatools -- function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end function addtag2(tag,text) local tg=tag:match("\\%d?%a+") text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") :gsub(tg.."[^\\}]+([^}]-)("..tg.."[^\\}]+)","%2%1") return text end function addtag3(tg,txt) no_tf=txt:gsub("\\t%b()","") tgt=tg:match("(\\%d?%a+)[%d%-&]") val="[%d%-&]" if not tgt then tgt=tg:match("(\\%d?%a+)%b()") val="%b()" end if not tgt then tgt=tg:match("\\fn") val="" end if not tgt then t_error("adding tag '"..tg.."' failed.") end if tgt:match("clip") then txt,r=txt:gsub("^({[^}]-)\\i?clip%b()","%1"..tg) if r==0 then txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end elseif no_tf:match("^({[^}]-)"..tgt..val) then txt=txt:gsub("^({[^}]-)"..tgt..val.."[^\\}]*","%1"..tg) elseif not txt:match("^{\\") then txt="{"..tg.."}"..txt elseif txt:match("^{[^}]-\\t") then txt=txt:gsub("^({[^}]-)\\t","%1"..tg.."\\t") else txt=txt:gsub("^({\\[^}]-)}","%1"..tg.."}") end return txt end function round(n,dec) dec=dec or 0 n=math.floor(n*10^dec+0.5)/10^dec return n end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function shiftsel2(sel,z,mode) if sel[z]sel[z] then sel[s]=sel[s]+1 end end end if mode==1 then table.insert(sel,sel[z]+1) end table.sort(sel) return sel end function textreplace(txt,r1,r2) txt=txt:gsub("^([^{]*)",function(t) t=t:gsub(r1,r2) return t end) txt=txt:gsub("(})([^{]*)",function(b,t) t=t:gsub(r1,r2) return b..t end) return txt end function textmod(orig,text) tk={} tg={} text=text:gsub("{\\\\k0}","") repeat text,r=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") until r==0 vis=text:gsub("{[^}]-}","") ltrz=re.find(vis,".") for l=1,#ltrz do table.insert(tk,ltrz[l].str) end stags=text:match(STAG) or "" text=text:gsub(STAG,"") :gsub("{[^\\}]-}","") count=0 for seq in orig:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end count=0 for seq in text:gmatch("[^{]-{%*?\\[^}]-}") do chars,as,tak=seq:match("([^{]-){(%*?)(\\[^}]-)}") pos=re.find(chars,".") if pos==nil then ps=0+count else ps=#pos+count end tgl={p=ps,t=tak,a=as} table.insert(tg,tgl) count=ps end newline="" for i=1,#tk do newline=newline..tk[i] newt="" for n, t in ipairs(tg) do if t.p==i then newt=newt..t.a..t.t end end if newt~="" then newline=newline.."{"..as..newt.."}" end end newtext=stags..newline text=newtext:gsub("{}","") return text end function trem(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") return tags end function cleantr(tags) trnsfrm="" for t in tags:gmatch("\\t%b()") do trnsfrm=trnsfrm..t end tags=tags:gsub("\\t%b()","") :gsub("^({[^}]*)}","%1"..trnsfrm.."}") return tags end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 tagz=tagz:gsub("(|i?clip%(%A-%))(.-)(\\i?clip%(%A-%))","%2%3") :gsub("(\\i?clip%b())(.-)(\\i?clip%b())",function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\%)]-%)","") return tagz end function extrakill(text,o) for i=1,#tags3 do tag=tags3[i] if o==2 then repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%3%2") until c==0 else repeat text,c=text:gsub("(\\"..tag.."[^\\}]+)([^}]-)(\\"..tag.."[^\\}]+)","%1%2") until c==0 end end repeat text,c=text:gsub("(\\pos[^\\}]+)([^}]-)(\\move[^\\}]+)","%1%2") until c==0 repeat text,c=text:gsub("(\\move[^\\}]+)([^}]-)(\\pos[^\\}]+)","%1%2") until c==0 return text end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function necroscopy(subs,sel) ADD=aegisub.dialog.display ak=aegisub.cancel ATAG="{%*?\\[^}]-}" STAG="^{\\[^}]-}" GUI={ {x=0,y=0,class="label",label="\\fax ",}, {x=1,y=0,width=2,class="floatedit",name="fax",value=lastfax or 0.05}, {x=0,y=1,width=2,class="checkbox",name="clax",label="from clip ",value=true}, {x=2,y=1,class="checkbox",name="right",label="to the right "},

{x=3,y=0,width=2,class="checkbox",name="cpstyle",label="copy style with tags",value=copy_style},

{x=5,y=0,class="label",label=" copy colours: ",}, {x=6,y=0,class="checkbox",name="c1",label="c"}, {x=7,y=0,class="checkbox",name="c3",label="3c "}, {x=8,y=0,class="checkbox",name="c4",label="4c "}, {x=9,y=0,class="checkbox",name="c2",label="2c"}, {x=10,y=0,width=2,class="checkbox",name="alfa",label="include alpha"},

{x=3,y=1,class="label",label="shift clip every frame by:",}, {x=5,y=1,width=2,class="floatedit",name="xlip",value=lastxlip or 0}, {x=7,y=1,width=3,class="floatedit",name="ylip",value=lastylip or 0},

{x=10,y=1,width=2,class="checkbox",name="splitg",label="split GUI",hint="open GUI to split by spaces/tags instead"}, {x=12,y=1,width=2,class="label",label=script_name.." v"..script_version}, } P,res=ADD(GUI, {"fax it","copy stuff","copy tags","copy text","copy clip","copy colours","3D shadow","split by \\N","cancel"},{cancel='cancel'})

if P=="cancel" then ak() end if P=="fax it" then fucks(subs,sel) end if P=="copy stuff" then copystuff(subs,sel) end if P=="copy tags" then copytags(subs,sel) end if P=="copy text" then copytext(subs,sel) end if P=="copy clip" then copyclip(subs,sel) end if P=="copy colours" then copycolours(subs,sel) end if P=="3D shadow" then shad3(subs,sel) end if P=="split by \\N" then sel=splitbreak(subs,sel) end lastfax=res.fax lastxlip=res.xlip lastylip=res.ylip aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacro(necroscopy) else aegisub.register_macro(script_name,script_description,necroscopy) end -- On pressing the assigned hotkey, cycles through the relevant sequence you see below, changing the value of the given tag. script_name="Cycles" script_description="Cycles blur, border, shadow, alpha, alignment" script_author="unanimated" script_version="1.8"

-- SETTINGS - You can change these sequences blur_sequence={"0.6","0.8","1","1.2","1.5","2","3","4","5","6","8","0.4","0.5"} bord_sequence={"0","1","2","3","4","5","6","7","8","9","10","11","12"} shad_sequence={"0","1","2","3","4","5","6","7","8","9","10","11","12"} alpha_sequence={"FF","00","10","30","60","80","A0","C0","E0"} align_sequence={"1","2","3","4","5","6","7","8","9"}

--[[ Adding more tags You could make this also work for the following tags: frz, frx, fry, fax, fay, fs, fsp, fscx, fscy, be, xbord, xshad, ybord, yshad by doing 3 things: 1. add a new sequence to the settings above for the tag you want to add 2. add a function below here based on what the others look like (all mentioned tags would be base 10) (it's adjusted for negative values too) 3. add "aegisub.register_macro("Cycles/YOUR_SCRIPT_NAME","Cycles WHATEVER_YOU_CHOOSE",FUNCTION_NAME_HERE)" at the end of the script If you at least roughly understand the basics, this should be easy. The main cycle function remains the same for all tags. Should you want to add other tags with different value patterns, check the existing exceptions for alpha in the cycle function. ]] function blur(subs,sel) sequence=blur_sequence base=10 tag="blur" cycle(subs,sel) end function bord(subs,sel) sequence=bord_sequence base=10 tag="bord" cycle(subs,sel) end function shad(subs,sel) sequence=shad_sequence base=10 tag="shad" cycle(subs,sel) end function alph(subs,sel) sequence=alpha_sequence base=16 tag="alpha" cycle(subs,sel) end function algn(subs,sel) sequence=align_sequence base=10 tag="an" cycle(subs,sel) end function cycle(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text text=text:gsub("\\t(%b())",function(t) return "\\t"..t:gsub("\\","|") end)

if tag=="alpha" then val1=text:match("^{[^}]-\\alpha&H(%x%x)&") else val1=text:match("^{[^}]-\\"..tag.."(%- ?[%d%.]+)") end if val1 then for n=1,#sequence do if val1==sequence[n] then val2=sequence[n+1] end end if val2==nil then for n=1,#sequence do if n>1 or sequence[1]~="FF" then if tonumber(val1,base)

text=text:gsub("{\\[^}]-}",function(t) return t:gsub("|","\\") end) line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro("Cycles/Blur Cycle","Cycles Blur",blur) aegisub.register_macro("Cycles/Border Cycle","Cycles Border",bord) aegisub.register_macro("Cycles/Shadow Cycle","Cycles Shadow",shad) aegisub.register_macro("Cycles/Alpha Cycle","Cycles Alpha",alph) aegisub.register_macro("Cycles/Alignment Cycle","Cycles Alignment",algn) -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#ibus script_name="iBus" script_description="Italy Bold Under Strike" script_author="unanimated" script_version="1.7" script_namespace="ua.iBus" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="1.7.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end function isub(subs,sel) for z,i in ipairs(sel) do l=subs[i] t=l.text sr=scheck(subs,l.style) v=1 if sr[sref] then v=0 end t=t:gsub("\\"..T.."([\\}])","\\"..T.."".. 1-v.."%1") if t:match("^{[^}]-\\"..T.."%d") then t=t:gsub("\\"..T.."(%d)",function(n) return "\\"..T.. 1-n end) else if t:match("\\"..T.."([01])") then iv=t:match("\\"..T.."([01])") end if iv==v then t=t:gsub("\\"..T.."(%d)",function(n) return "\\"..T.. 1-n end) end t="{\\"..T..v.."}"..t t=t:gsub("{(\\%a%d})({\\[^}]*)}","%2%1") end l.text=t subs[i]=l end end function scheck(subs,sn) for i=1,#subs do if subs[i].class=="style" then if sn==subs[i].name then sr=subs[i] break end end end return sr end function ita(subs,sel) T="i" sref="italic" isub(subs,sel) end function bol(subs,sel) T="b" sref="bold" isub(subs,sel) end function und(subs,sel) T="u" sref="underline" isub(subs,sel) end function str(subs,sel) T="s" sref="strikeout" isub(subs,sel) end if haveDepCtrl then depRec:registerMacros({ {"iBus/Italics",script_description,ita}, {"iBus/Bold",script_description,bol}, {"iBus/Underline",script_description,und}, {"iBus/Strikeout",script_description,str} },false) else aegisub.register_macro("iBus/Italics",script_description,ita) aegisub.register_macro("iBus/Bold",script_description,bol) aegisub.register_macro("iBus/Underline",script_description,und) aegisub.register_macro("iBus/Strikeout",script_description,str) end -- First function inserts a linebreak where it seems logical - after periods, commas, before "and", etc. If the line has one, it removes it. -- This will not always produce the desired result, but mostly it works fairly well. -- There's a limit for how unevenly a line can be split, so if it's over the ratio, it nukes the \N and goes to the next step. -- If the stuff above didn't work out, a linebreak is inserted in the middle of the line if restrictions in settings allow it. -- If it doesn't pass set restrictions, it opens a small GUI. Click where you want the break, hit enter, click OK. -- The "All spaces" option is useful for typesetters when they want each word on a new line. -- Second function puts a linebreak after the first word. Every new run of the function puts the linebreak one word further. -- When it reaches the last word, it removes the break. -- Third function shifts linebreaks back and works line the 2nd. -- You can bind each function to a different hotkey and combine them as needed. -- To bring up Setup, type 'setup' into the effect field. -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#linebreak script_name="Line Breaker" script_description="insert/shift linebreaks" script_author="unanimated" script_version="2.3" script_namespace="ua.LineBreaker" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="2.3.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end re=require'aegisub.re' function setupcheck() file=io.open(breaksetup) if file==nil then setup() end end nnngui={ {x=0,y=0,width=2,class="label",label="------Line Breaker Setup ------"}, {x=0,y=1,class="label",label="min. characters:"}, {x=1,y=1,class="intedit",name="minchar",value=0}, {x=0,y=2,class="label",label="min. words:"}, {x=1,y=2,class="intedit",name="minword",value=3}, {x=0,y=3,width=2,class="checkbox",name="middle",label="linebreak in the middle if all else fails",value=true}, {x=0,y=4,class="label",label="^ min. characters:"}, {x=1,y=4,class="intedit",name="midminchar",value=0}, {x=0,y=5,width=3,class="checkbox",name="forcemiddle",label="force breaks in the middle",hint="rather than after commas etc."}, {x=0,y=6,width=3,class="checkbox",name="disabledialog",label="disable dialog for making manual breaks"}, {x=0,y=7,width=3,class="checkbox",name="allowtwo",label="allow a break if there are only two words",value=true}, {x=0,y=8,width=3,class="checkbox",name="balance",label="enable balance checks",value=true,hint="check ratio between top and bottom line"}, {x=0,y=9,class="label",label="^ max. ratio:"}, {x=1,y=9,class="intedit",name="maxratio",value=2.2}, {x=0,y=10,width=3,class="checkbox",name="nobreak1",label="don't break 1-liners",hint="disables manual breaking && break between 2"} } function setup() file=io.open(breaksetup) if file~=nil then konf=file:read("*all") io.close(file) for key,val in ipairs(nnngui) do if val.class=="checkbox" or val.class=="intedit" then if konf:match(val.name) then val.value=detf(konf:match(val.name..":(.-)\n")) end end end end P,res=ADD(nnngui,{"Save","Cancel"},{ok='Save',close='Cancel'}) if P=="Cancel" then ak() end nnncf="Line Breaker Settings\n\n" for key,val in ipairs(nnngui) do if val.class=="checkbox" then nnncf=nnncf..val.name..":"..tf(res[val.name]).."\n" end if val.class=="intedit" then nnncf=nnncf..val.name..":"..res[val.name].."\n" end end

file=io.open(breaksetup,"w") file:write(nnncf) file:close() ADD({{class="label",label="Config Saved to:\n"..breaksetup.."\nTo bring up the setup again, type 'setup' into 'effect'."}},{"OK"},{close='OK'}) ak() end function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=tonumber(txt) end return ret end function readconfig() file=io.open(breaksetup) if file~=nil then konf=file:read("*all") io.close(file) min_characters=detf(konf:match("minchar:(.-)\n")) min_words=detf(konf:match("minword:(.-)\n")) put_break_in_the_middle=detf(konf:match("middle:(.-)\n")) middle_min_char=detf(konf:match("midminchar:(.-)\n")) force_middle=detf(konf:match("forcemiddle:(.-)\n")) disable_dialog=detf(konf:match("disabledialog:(.-)\n")) allow_two=detf(konf:match("allowtwo:(.-)\n")) balance_checks=detf(konf:match("balance:(.-)\n")) max_ratio=detf(konf:match("maxratio:(.-)\n")) do_not_break_1liners=detf(konf:match("nobreak1:(.-)\n")) end end function nnn(subs,sel) ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel breaksetup=ADP("?user").."\\lbreak.conf" setupcheck() readconfig() for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if st.name=="Default" then defaref=st defleft=st.margin_l defright=st.margin_r end end if subs[i].class=="info" then local k=subs[i].key local v=subs[i].value if k=="PlayResX" then resx=v end if k=="PlayResY" then resy=v end end if subs[i].class=="dialogue" then break end end

for x,i in ipairs(sel) do line=subs[i] text=line.text if line.effect=="setup" then setup() end if aegisub.progress.is_cancelled() then ak() end aegisub.progress.title("Processing line: "..x.."/"..#sel)

if line.style=="Default" then styleref=defaref else styleref=stylechk(subs,line.style) end

-- remove linebreak if there is one if text:match("\\N") then text=text :gsub("%s*{\\i0}\\N{\\i1}%s*"," ") :gsub("%*","_ast_") :gsub("\\[Nn]","*") :gsub("%s*%*+%s*"," ") :gsub("_ast_","*") else

text=text:gsub("([%.,%?!]) $","%1")

nocom=text:gsub("%b{}","") nocomlength=nocom:len() tags=text:match("^{\\[^}]-}") if tags==nil then tags="" end stekst=text:gsub("^{\\[^}]-}","") repeat stekst,r=stekst:gsub("{[^\\}]-}$","") until r==0 tekst=stekst -- fill spaces in comments for s in tekst:gmatch("{[^\\}]-}") do s2=s:gsub(" ","__") tekst=tekst:gsub(esc(s),s2) end

-- get max width of a line in pixels width,height,descent,ext_lead=aegisub.text_extents(styleref,nocom) xres,yres,ar,artype=aegisub.video_size() if xres==nil then xres=resx yres=resy end realx=xres/yres*resy wf=realx/resx if line.style=="Default" then vidth=realx-(defleft*wf)-(defright*wf) else vidth=realx-(styleref.margin_l*wf)-(styleref.margin_r*wf) end

-- count words wrd=0 for word in nocom:gmatch("%S+") do wrd=wrd+1 end

-- put breaks after . , ? ! that are not at the end tekst=tekst:gsub("([^%.])%.%s","%1. \\N") :gsub("([^%.])%.({\\[^}]-})%s","%1.%2 \\N") :gsub("([,!%?:;])%s","%1 \\N") :gsub("([,!%?:;])({\\[^}]-})%s","%1%2 \\N") :gsub("([%.,])\"%s","%1\" \\N") :gsub("%.%.%. ","... \\N") :gsub("([DM][rs]s?%.) \\N","%1 ")

-- remove comma breaks if . or ? or ! if tekst:match("[%.%?!] \\N") and tekst:match(", \\N") then tekst=tekst:gsub(", \\N",", ") end

tekst=reduce(tekst) -- remove breaks if there are more than one; leave the one closer to the centre tekst=balance(tekst) -- balance of lines - ratio check 1

-- if breaks removed and there's a comma if not tekst:match("\\N") then tekst=tekst:gsub(",%s",", \\N") :gsub(",({\\[^}]-})%s",",%1 \\N") :gsub("^([%w']+, )\\N","%1") :gsub(", \\N([%w%p]+)$",", %1") end tekst=reduce(tekst)

-- balance of lines - ratio check 2 ratio=nil tekst=balance(tekst) backup1=nil if tekst:match("\\N") and ratio~=nil and ratio>=2 and max_ratio>ratio then backup1=tekst ratio1=ratio tekst=db(tekst) end

if wrd>5 then testxt=tekst:gsub("^[%w%p]+ [%w%p]+(.-)[%w%p]+ [%w%p]+$","%1") else testxt=tekst end

-- if no linebreak in line, put breaks before selected words, in 3 rounds words1={" but "," and "," if "," when "," because "," 'cause "," yet "," unless "," with "," without "," whether "," where "} words2={" or "," nor "," for "," from "," before "," after "," at "," that "," since "," until "," while "," behind "," than "," over "} words3={" about "," into "," to "," how "," is "," isn't "," was "," wasn't "," are "," aren't "," were "," weren't "} tekst=words(words1) tekst=words(words2) tekst=words(words3)

-- insert break in the middle of the line if force_middle then tekst=db(tekst) end if put_break_in_the_middle and nocomlength>=middle_min_char and not tekst:match("\\N") then tekst="\\N"..tekst diff=250 stop=0 while stop==0 do last=tekst repeat tekst,r1=tekst:gsub("\\N(%b{})","%1\\N") tekst,r2=tekst:gsub("\\N([^%s{}]+)","%1\\N") until r1==0 and r2==0 tekst=tekst:gsub("\\N%s"," \\N") btxt=tekst:gsub("%b{}","") beforespace=btxt:match("^(.-)\\N") beforelength=beforespace:len() afterspace=btxt:match("\\N(.-)$") afterlength=afterspace:len() tdiff=math.abs(beforelength-afterlength) if tdiff

-- shift breaks to better places backup2=tekst tekst=re.sub(tekst," (a|a[sn]|by|I|I'm|I'd|I've|I'll|the|for|that|o[nfr]|i[nf]|who|to) \\\\N([\\w\\-']+) "," \\\\N\\1 \\2 ") tekst=re.sub(tekst," \\\\N([oi]n) (because|and|but|when) "," \\1 \\\\N\\2 ") tekst=tekst :gsub(" (lots?) \\Nof "," %1 of \\N") :gsub(" \\Nme "," me \\N") :gsub("^ ","") tekstb=balance(tekst) if tekstb~=tekst then tekst=backup2 end

double={"so that","no one","ought to","now that","it was","he was","she was","will be","there is","there are","there was","there were","get to","sort of","kind of","put it","each other","each other's","have to","has to","had to","having to","want to","wanted to","used to","able to","going to","supposed to","allowed to","tend to","due to","forward to","thanks to","not to","has been","have been","had been","filled with","full of","out of","into the","onto the","part with","more than","less than","make sure","give up","would be","wipe out","wiped out","real life","no matter","based on","bring up","think of","thought of","even if","even when","even though","grow up","grew up","grown up","other than"} for d=1,#double do dbl=double[d] d1,d2=dbl:match("([%a']+) ([%a']+)") btxt=tekst:gsub("%b{}","") if tekst:match(" "..d1.." \\N"..d2.." ") then bd=btxt:match("^(.-)"..d1.." \\N"..d2) bd=bd:gsub("%b{}","") blgth=bd:len() ad=btxt:match(d1.." \\N"..d2.."(.-)$") ad=ad:gsub("%b{}","") algth=ad:len() if blgth>algth then tekst=tekst:gsub(" "..d1.." \\N"..d2.." "," \\N"..d1.." "..d2.." ") else tekst=tekst:gsub(" "..d1.." \\N"..d2.." "," "..d1.." "..d2.." \\N") end end end nobreak={"sort of","kind of","full of","out of","based on","think of","thought of","even if","even when"} nb=0 for b=1,#nobreak do if tekst:match(nobreak[b].." \\N") then nb=1 end end if nb==0 then tekst=re.sub(tekst," (a|a[sn]|by|I|I'm|I'd|I've|I'll|the|for|o[nfr]|i[nf]|who) \\\\N([\\w\\-']+) "," \\\\N\\1 \\2 ") end if tekst:match(" by %a+ing \\N") then beforethat=tekst:match("^(.-)by %a+ing \\N") beforethat=beforethat:gsub("%b{}","") befrlgth=beforethat:len() afterthat=tekst:match("by %a+ing \\N(.-)$") afterthat=afterthat:gsub("%b{}","") afterlgth=afterthat:len() if befrlgth>afterlgth then tekst=tekst:gsub(" (by %a+ing) \\N"," \\N%1 ") end end

if not tekst:match("\\N") and backup1~=nil then tekst=backup1 end if tekst:match("\\N") and backup1~=nil and ratio>=ratio1 then tekst=backup1 end

-- character/word restrictions if nocomlength

-- break if there are only 2 words in the line if wrd==2 and allow_two then tekst=tekst:gsub("(%w+%p?)%s(%w+%p?)%s?","%1 \\N%2") end

-- don't break 1-liners if in settings if do_not_break_1liners and vidth>=width then tekst=db(tekst) end

-- apply changes tekst=tekst:gsub("__"," ") stekst=esc(stekst) tekst=tekst:gsub("%%","%%%%") text=text:gsub(stekst,tekst)

-- GUI for manual breaking if disable_dialog==false and not do_not_break_1liners and not text:match("\\N") or line.effect=="n" then after=text:gsub("^{\\[^}]-}",""):gsub(" *\\[Nn] *"," ") if not ALLSP then dialog={{x=0,y=0,width=2,height=5,class="textbox",name="txt",value=after}, {x=0,y=5,class="label",label="Use 'Enter' to make linebreaks "}, {x=1,y=5,class="checkbox",name="allspaces",label="'All spaces' for all lines "}} buttons={"OK","All spaces","Skip","Cancel"} pressed,res=aegisub.dialog.display(dialog,buttons,{close='Cancel'}) end if pressed=="Cancel" then ak() end if pressed=="Skip" then text=line.text end if pressed=="OK" then res.txt=res.txt:gsub("\n","\\N") :gsub("\\N "," \\N") text=tags..res.txt end if pressed=="All spaces" then if res.allspaces then ALLSP=true end after=after:gsub("%s+"," \\N") :gsub("\\N\\N","\\N") text=tags..after end if line.effect=="n" then line.effect="" end end end

line.text=text subs[i]=line end ALLSP=nil aegisub.set_undo_point(script_name) return sel end function balance(tekst) if balance_checks and tekst:match("\\N") and not tekst:match("\\N%-") and wrd>4 then beforespace=tekst:match("^(.-)%s*\\N") beforespace=beforespace:gsub("%b{}","") beforelength=beforespace:len() afterspace=tekst:match("\\N(.-)$") afterspace=afterspace:gsub("%b{}","") afterlength=afterspace:len() if beforelength>afterlength then ratio=beforelength/afterlength else ratio=afterlength/beforelength end difflength=math.abs(beforelength-afterlength) wb=aegisub.text_extents(styleref,beforespace) wa=aegisub.text_extents(styleref,afterspace) if wb>wa then ratiop=wb/wa else ratiop=wa/wb end if ratio>max_ratio then tekst=db(tekst) end if nocomlength>50 and ratio>(max_ratio*0.95) or ratiop>(max_ratio*0.95) then tekst=db(tekst) end if nocomlength>70 and ratio>(max_ratio*0.9) or ratiop>(max_ratio*0.9) then tekst=db(tekst) end -- aegisub.log("\n ratio: "..ratio.." length: "..nocomlength) aegisub.log("\n ratiop: "..ratiop) -- prevent 3-liners if wb>=vidth or wa>=vidth then tekst=db(tekst) end end return tekst end function reduce(tekst) if tekst:match("\\N.+\\N") then repeat beforespace,afterspace=tekst:match("^(.-)\\N.*\\N(.-)$") beforespace=beforespace:gsub("%b{}","") beforelength=beforespace:len() afterspace=afterspace:gsub("%b{}","") afterlength=afterspace:len() if beforelength>afterlength then tekst=tekst:gsub("^(.*)\\N(.-)$","%1%2") else tekst=tekst:gsub("^(.-)\\N","%1") end until not tekst:match("\\N.+\\N") end return tekst end function words(tab) if not tekst:match("\\N") and wrd>4 then for w=1,#tab do ord=tab[w] if testxt:match(ord) then tekst=tekst:gsub(ord," \\N"..ord) :gsub("\\N ","\\N") end end tekst=reduce(tekst) tekst=balance(tekst) end return tekst end function db(t) t=t:gsub("\\N","") return t end function logg(m) m=tf(m) or "nil" aegisub.log("\n "..m) end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function t_error(message,cancel) aegisub.dialog.display({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then aegisub.cancel() end end function stylechk(subs,sn) for i=1,#subs do if subs[i].class=="style" then local st=subs[i] if sn==st.name then sr=st break end end end if sr==nil then t_error("Style '"..sn.."' doesn't exist.",1) end return sr end function nshift(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text text=text:gsub("([%a%p])\\N([%a%p])","%1 \\N%2") if not text:match("\\N") then text="\\N"..text end text=text:gsub("\\N([^%s{}]+%s?)$","%1") -- end text=text:gsub("\\N([^%s{}]+%s?%b{}%s?)$","%1") -- end text=text:gsub("\\N%s"," \\N") repeat text,r1=text:gsub("\\N(%b{})","%1\\N") text,r2=text:gsub("\\N([^%s{}]+)","%1\\N") until r1==0 and r2==0 text=text:gsub("\\N%s"," \\N") text=text:gsub("\\N$","") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end function backshift(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text text=text:gsub("([%a%p])\\N([%a%p])","%1 \\N%2") if not text:match("\\N") then text=text.."\\N" end text=text:gsub("^(%b{}%s?[^%s{}]+%s?)\\N","%1") -- start text=text:gsub("^([^%s{}]+%s?)\\N","%1") -- start text=text:gsub("%s\\N","\\N ") repeat text,r1=text:gsub("(%b{})\\N","\\N%1") text,r2=text:gsub("([^%s{}]+)\\N","\\N%1") until r1==0 and r2==0 text=text:gsub("^\\N","") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end if haveDepCtrl then depRec:registerMacros({ {"Line Breaker/Insert Linebreak",script_description,nnn}, {"Line Breaker/Shift Linebreak",script_description,nshift}, {"Line Breaker/Shift Linebreak Back",script_description,backshift} },false) else aegisub.register_macro("Line Breaker/Insert Linebreak",script_description,nnn) aegisub.register_macro("Line Breaker/Shift Linebreak",script_description,nshift) aegisub.register_macro("Line Breaker/Shift Linebreak Back",script_description,backshift) end -- Manual: http://unanimated.xtreemhost.com/ts/scripts-manuals.htm#join

-- JOIN -- joins selected lines or one selected line with the following line. it's a combination of "Join (concatenate)" and "Join (keep first)" -- if the text (without tags) is the same on all lines, then it's "keep first" -- if different, it's "concatenate" for 2 lines, but it nukes some redundant tags from the 2nd line -- if it's more than 2 lines with different text, you get to choose to join them with only tags from the first one, or from all -- when keeping tags, it nukes ones that should only be once in a line plus some others detected as redundant (not very sophisticated) -- set a simple hotkey to use when timing

-- SPLIT -- splits a line at linebreak (use together with Line Breaker with simple hotkeys under Subtitle Grid) -- it's similar to "Split at cursor (estimate times)", but uses \N as "cursor" -- compared to the inbuilt tool, the times estimation works better, you keep tags for both resulting lines, and it snaps to keyframes (6-frame range)

-- SNAP -- snaps to keyframes or adjacent lines based on the settings below

-- KF SNAPPING SETTINGS kfsb=6 -- starts before kfeb=10 -- ends before kfsa=8 -- starts after kfea=15 -- ends after

-- END OF SETTINGS script_name="Join / Split / Snap" script_description="Joins lines / splits lines / snaps to keyframes" script_author="unanimated" script_version="1.2" script_namespace="ua.JoinSplitSnap" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="1.2.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end function join(subs,sel) go=0 morethan2=nil if #sel>1 then go=1 st=10000000 et=0 for z,i in ipairs(sel) do l=subs[i] t=l.text ct=t:gsub("%b{}","") stm=l.start_time etm=l.end_time if stmet then et=etm end if z>1 and ct~=ref then go=0 end ref=ct end end if go==1 then l=subs[sel[1]] l.start_time=st l.end_time=et subs[sel[1]]=l for i=#sel,2,-1 do subs.delete(sel[i]) end sel={sel[1]} else if sel[1]==#subs then aegisub.log("Nothing to join with.") aegisub.cancel() end if #sel==1 then table.insert(sel,sel[1]+1) end if #sel>2 then morethan2=true dialog={{class="label",label="Join "..#sel.." lines with different text?\n\nOption 1: join, keeping only tags from first line\n\nOption 2: join and try to preserve relevant tags"}} buttons={"Join, no tags","Join, with tags","Cancel"} press=aegisub.dialog.display(dialog,buttons,{close='Cancel'}) if press=="Join, no tags" then nt="" et=0 for i=2,#sel do l=subs[sel[i]] t=l.text:gsub("%b{}",""):gsub(" *\\N *"," ") nt=nt.." "..t if l.end_time>et then et=l.end_time end end l=subs[sel[1]] l.text=l.text:gsub(" *\\N *"," ")..nt l.end_time=et subs[sel[1]]=l for i=#sel,2,-1 do subs.delete(sel[i]) table.remove(sel,i) end end if press=="Join, with tags" then repeat join2(subs,sel) until #sel==1 end else join2(subs,sel) end end aegisub.set_undo_point(script_name) return sel end function join2(subs,sel) l=subs[sel[1]] t=l.text ct=t:gsub("%b{}","") l2=subs[sel[2]] t2=l2.text ct2=t2:gsub("%b{}","") ct3=t2:gsub("^{[^}]-}",""):gsub("^%- ","") if ct~=ct2 or morethan2 then t=t:gsub("{[Jj][Oo][Ii][Nn]}%s*$","") tt=t:match("^{\\[^}]-}") or "" tt2=t2:match("^{\\[^}]-}") or "" tt1=tt:gsub("\\an?%d",""):gsub("\\%a%a+%b()",""):gsub("\\q%d",""):gsub("\\t%([^\\]*%)",""):gsub("{}","") tt2=tt2:gsub("\\an?%d",""):gsub("\\%a%a+%b()",""):gsub("\\q%d",""):gsub("\\t%([^\\]*%)",""):gsub("{}","") if tt==tt2 then t=t.." "..ct3 elseif not tt1:match("\\t") and not tt2:match("\\t") then for tag in tt1:gmatch("\\[^\\}]+") do tt2=tt2:gsub(esc(tag),"") end t=t.." "..tt2..ct3 else t=t.." "..tt2..ct3 end t=t:gsub("%.%.%. %.%.%."," "):gsub("(%a%-) (%a)","%1%2"):gsub("\" \""," "):gsub(" *\\N *"," ") :gsub("(\\i1.-)\\i(%d)",function(i,n) if n=="1" then return i else return i..n end end) :gsub("{}","") end if l2.end_time>l.end_time then l.end_time=l2.end_time end subs.delete(sel[2]) l.text=t subs[sel[1]]=l if #sel>1 then table.remove(sel,2) if #sel>1 then for s=2,#sel do sel[s]=sel[s]-1 end end end return subs[sel[1]] end function split(subs,sel) for i=#sel,1,-1 do line=subs[sel[i]] text=line.text c=0

if not text:match("\\N") and sel[i]<#subs then nextline=subs[sel[i]+1] if text:match(" that$") then text=text:gsub(" that$","") nextline.text="that "..nextline.text c=1 end if text:match(" and$") then text=text:gsub(" and$","") nextline.text="and "..nextline.text c=1 end if text:match(" but$") then text=text:gsub(" but$","") nextline.text="but "..nextline.text c=1 end if text:match(" so$") then text=text:gsub(" so$","") nextline.text="so "..nextline.text c=1 end if text:match(" to$") then text=text:gsub(" to$","") nextline.text="to "..nextline.text c=1 end if text:match(" when$") then text=text:gsub(" when$","") nextline.text="when "..nextline.text c=1 end if text:match(" with$") then text=text:gsub(" with$","") nextline.text="with "..nextline.text c=1 end if text:match(" the$") then text=text:gsub(" the$","") nextline.text="the "..nextline.text c=1 end subs[sel[i]+1]=nextline end

if c==0 then text=text:gsub("{SPLIT}","{split}") if not text:match("\\N") and text:match("{split}") then text=text:gsub("{split}","\\N") end if not text:match("\\N") and text:match("%- ") then text=text:gsub("(.)%- (.-)$","%1\\N- %2") end if not text:match("\\N") and text:match("%. [{\\\"]?%w") then text=text :gsub("([MD][rs]s?)%. ","%1## ") :gsub("^(.-)%. ","%1. \\N") :gsub("## ",". ") end if not text:match("\\N") and text:match("[%?!] {?%w") then text=text:gsub("^(.-)([%?!]) ","%1%2 \\N") end if not text:match("\\N") and text:match(", {?%w") then text=text:gsub("^(.-), ","%1, \\N") end if text:match("\\N") and text:match("{split}") then text=text:gsub("\\N","/N"):gsub("{split}","\\N") end end

if not text:match("\\N") and not text:match(" ") then text=text.."\\N"..text end

if text:match("\\N") then text=text:gsub("^%- (.-\\N)%- ","%1"):gsub("^({\\i1})%- (.-\\N)%- ","%1%2"):gsub("({\\i1})%- ","%1"):gsub("{add}","") line2=line start=line.start_time endt=line.end_time dur=endt-start ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame keyframes=aegisub.keyframes() startf=ms2fr(start) endf=ms2fr(endt) diff=250 diffe=250 startkf=keyframes[1] endkf=keyframes[#keyframes] txt=text:gsub("%b{}","") one,two=txt:match("^(.-)\\N(.*)") c1=one:len() c2=two:len() f=c1/(c1+c2) if dur<3200 then f=(f+0.5)/2 end if dur<2000 then f=0.5 end if f<0.2 then f=0.2 end if f>0.8 then f=0.8 end

-- line 2 aftern=text:match("\\N%s*(.*)") tags=text:match("^{\\[^}]-}") if tags and not aftern:match("^{\\[^}]-}") then aftern=tags..aftern end line2.text=aftern:gsub("/N","\\N") line2.start_time=start+dur*f start2f=ms2fr(line2.start_time) for k,kf in ipairs(keyframes) do if kf>=start2f-6 and kf<=start2f+6 then tdiff=math.abs(start2f-kf) if tdiff<=diff then diff=tdiff startkf=kf end start2=fr2ms(startkf) line2.start_time=start2 end end subs.insert(sel[i]+1,line2)

-- line 1 text=text:gsub("^(.-)%s?\\N(.*)","%1"):gsub("/N","\\N") line.start_time=start line.end_time=start+dur*f end1f=ms2fr(line.end_time) for k,kf in ipairs(keyframes) do if kf>=end1f-12 and kf<=end1f+6 then tdiff=math.abs(end1f-kf) if tdiff500 then line.end_time=endt end end end end line.text=text subs[sel[i]]=line end for s=#sel,1,-1 do sel[s]=sel[s]+s-1 end aegisub.set_undo_point(script_name) return sel end function keyframesnap(subs,sel) keyframes=aegisub.keyframes() ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame if subs[sel[1]].effect=="gui" then gui={ {x=0,y=0,class="label",label="Starts before "}, {x=0,y=1,class="label",label="Ends before "}, {x=0,y=2,class="label",label="Starts after "}, {x=0,y=3,class="label",label="Ends after "}, {x=1,y=0,class="floatedit",name="sb",value=6}, {x=1,y=1,class="floatedit",name="eb",value=10}, {x=1,y=2,class="floatedit",name="sa",value=8}, {x=1,y=3,class="floatedit",name="ea",value=15} } buttons={"OK","Cancel"} pressed,res=aegisub.dialog.display(gui,buttons,{ok='OK',close='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end kfsb=res.sb kfeb=res.eb kfsa=res.sa kfea=res.ea end

for z,i in ipairs(sel) do line=subs[i] start=line.start_time endt=line.end_time startn=start endtn=endt startf=ms2fr(start) endf=ms2fr(endt) diff=250 diffe=250 KS=0 KE=0 startkf=keyframes[1] endkf=keyframes[#keyframes]

-- snap to keyframes for k,kf in ipairs(keyframes) do if kf>=startf-kfsa and kf<=startf+kfsb then sdiff=math.abs(startf-kf) if sdiff<=diff then diff=sdiff startkf=kf startn=fr2ms(startkf) KS=1 end end if kf>=endf-kfea and kf<=endf+kfeb then ediff=math.abs(endf-kf) if ediff

-- snap to adjacent lines if KS==0 then if subs[i-1].class=="dialogue" then l2=subs[i-1] prevend=l2.end_time pref=ms2fr(prevend) sdiff=startf-pref if sdiff<=kfsa and sdiff>0 or sdiff<0 and sdiff<=kfsb then startn=prevend l2.end_time=fr2ms(ms2fr(prevend)) subs[i-1]=l2 end end end if KE==0 then if subs[i+1] then l2=subs[i+1] nextart=l2.start_time nesf=ms2fr(nextart) ediff=nesf-endf if ediff<=kfea and ediff>0 or ediff<0 and ediff<=kfeb then endtn=nextart l2.start_time=fr2ms(ms2fr(nextart)) subs[i+1]=l2 end end end

if startn==nil then startn=start end if endtn==nil then endtn=endt end if startn~=line.start_time then startn=fr2ms(ms2fr(startn)) end if endtn~=line.end_time then endtn=fr2ms(ms2fr(endtn)) end line.start_time=startn line.end_time=endtn subs[i]=line end aegisub.set_undo_point(script_name) return sel end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function logg(m) m=m or "nil" aegisub.log("\n "..m) end if haveDepCtrl then depRec:registerMacros({ {"Join-Split-Snap/Join","Joins lines",join}, {"Join-Split-Snap/Split","Splits Lines",split}, {"Join-Split-Snap/Snap to keyframes","Snaps to nearby keyframes",keyframesnap}, },false) else aegisub.register_macro("Join-Split-Snap/Join","Joins lines",join) aegisub.register_macro("Join-Split-Snap/Split","Splits Lines",split) aegisub.register_macro("Join-Split-Snap/Snap to keyframes","Snaps to nearby keyframes",keyframesnap) end --[[ Aladin's Lamp

Description:

This script tries to solve 4 problems when dealing with Arabic text in Aegisub. 1. Inline tags that mess up the order of the text 2. Line breaks that mess up the order of the text when there are inline tags 3. Punctuation that ends up on the wrong end of the line 4. English text that messes up the order of Arabic text

1 - Inline tags - {tags1}text1{tags2}text2{tags3}text3 The script sorts the line in 3-2-1 order and fills in tags as required, including getting values from styles. You should be able to input any number of inline tags, run the script, and get back the correct sentence order. You must set all tags first and then run the script, not after each tag! Running the script twice should revert the line to the original state. So if you want to add another tag to an already fixed line, run the script (this will revert the order), add the new tag(s), and run the script again.

2 - Line breaks - The script switches the top and bottom parts of the line and sorts out the tags. This doesn't use styles, so if some tags like colours are only defined for the part after the line break, you should set them for the first part as well (though you can do it just as easily after). It may or may not work correctly with transforms.

3 - Fix punctuation - This moves a punctuation mark from the right end of the line to the left, or if there's a line break, after the line break. This way the punctuation -should- always end up at the end of the sentence.

4 - English Text - If you put English text in the middle of Arabic text, the order of the parts before and after it gets switched. This should, again, switch the parts back correctly. This detects only ordinary Latin characters and won't work with things like "ä".

Regarding editing issues, Multi-Line Editor might work better than Aegisub's Edit Box, since the lua interface seems to have some right-to-left support. http://unanimated.xtreemhost.com/ts/multiedit.lua

Note: I don't know any Arabic, so this may have any number of bugs I'm not aware of. Working with right-to-left text that I don't understand is rather confusing.

--]] script_name="Aladin's Lamp" script_description="Attempts to deal with Arabic text" script_author="unanimated" script_version="1.1" function fixnline(subs,sel) styleget(subs) for z,i in ipairs(sel) do line=subs[i] text=line.text sr=stylechk(line.style) if text:match(".{%*?\\") then text=rvrs(text) end line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end stags={"\\fsize","\\fscx","\\fscy","\\fsp","\\1c","\\2c","\\3c","\\4c","\\1a","\\2a","\\3a","\\4a","\\bord","\\shad","\\bold","\\i ta","\\u","\\str","\\frz","\\fn"} sty={"fontsize","scale_x","scale_y","spacing","color1","color2","color3","color4","color1","color2","color3","color4"," outline","shadow","bold","italic","underline","strikeout","angle","fontname"} mtags={"\\blur","\\be","\\frx","\\fry","\\fax","\\fay","\\xbord","\\xshad","\\ybord","\\yshad","\\alpha"} function rvrs(text) txtab={} text=text:gsub("\\t%b()",function(t) return t:gsub("\\","|") end)

:gsub("\\fs(%d)","\\fsize%1"):gsub("\\c&","\\1c&"):gsub("\\b(%d)","\\bold%1"):gsub("\\i(%d)","\\ita%1"):gsub("\\s(%d) ","\\str%1") :gsub("( +)({\\[^}]-})","%2%1") tags=text:match("^{\\[^}]-}") or "" if not text:match("^{\\[^}]-}") then text="{\\tags} "..text else text=text:gsub("^{\\[^}]-}","{\\tags} ") end punct=text:match("{\\tags} ([%.,%?!:;—])") or text:match("{\\tags} (—)") or text:match("{\\tags} ()") or "" text=text:gsub("({\\tags} )"..punct,"%1") intags="" for tg in text:gmatch(".{(\\[^}]-)}") do intags=intags..tg end for s=1,#stags do tag=stags[s] if intags:match(tag) then if tags:match(tag) then styletag=tags:match(tag.."[^\\}]+") else styletag=sr[sty[s]] if tag:match("\\%dc") then styletag=styletag:gsub("H%x%x","H") end if tag:match("\\%da") then styletag=styletag:match("H%x%x") end if styletag==true then styletag=1 end if styletag==false then styletag=0 end styletag=tag..styletag end text=addtag(styletag,text) end end for m=1,#mtags do tag=mtags[m] if intags:match(tag) then if tags:match(tag) then styletag=tags:match(tag.."[^\\}]+") else styletag="0" if tag:match("\\alpha") then styletag="&H00&" end styletag=tag..styletag end text=addtag(styletag,text) end end for g,x in text:gmatch("({\\[^}]-})([^{]+)") do table.insert(txtab,g..x) end nt="" for t=#txtab,1,-1 do nt=nt..txtab[t] end nt=tags..nt nt=nt:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}")

:gsub("\\tags",""):gsub("\\fsize","\\fs"):gsub("\\1c","\\c"):gsub("\\bold","\\b"):gsub("\\ita","\\i"):gsub("\\str","\\s"):gsub(" *$","") :gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) :gsub("^({\\[^}]-}) +","%1") :gsub("^({\\[^}]-})","%1"..punct) :gsub("|t%b()",function(t) return t:gsub("|","\\") end) return nt end function fixbreak(subs,sel) styleget(subs) for z,i in ipairs(sel) do line=subs[i] text=line.text sr=stylechk(line.style) if text:match("\\N") and text:match(".{\\") then text=text:gsub("\\t(%b())",function(t) return "\\t"..t:gsub("\\","/") end) tags=text:match("^{\\[^}]-}") or "" text=text:gsub("({\\[^}]-}) *\\N","\\N%1") :gsub("({\\[^}]-}) *([^{]+%S)%s*\\N *([^{]+)","%1 %2\\N%1 %3") T1,T2=text:match("^(.-)\\N(.*)$") T1=T1:gsub("\\%a%a+%b()",""):gsub("\\an%d",""):gsub("{}","") tagsT1=T1:match("^{\\[^}]-}") or "" for tg in tagsT1:gmatch("(\\%a+)[%d%.%-]+") do if not T2:match(tg) then tagsT1=tagsT1:gsub(tg.."[%d%.%-]+","") end end for tg in tagsT1:gmatch("(\\%d?%a+)&H%x+&") do if not T2:match(tg) then tagsT1=tagsT1:gsub(tg.."&H%x+&","") end end T1=T1:gsub("^{\\[^}]-}","") tagsT1=tagsT1:gsub("{}","") T1=tagsT1..T1 text=tags..T2.."\\N"..T1

text=text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") :gsub("({%*?\\[^}]-})",function(tg) return duplikill(tg) end) :gsub("\\t%b()",function(t) return t:gsub("/","\\") end) end line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end function fixeng(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text :gsub("^([^{]*)",function(t) return engswitch(t) end) :gsub("}([^{]*)",function(t) return "}"..engswitch(t) end) :gsub("^({\\[^}]-}) ","%1") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end function engswitch(t) ttab={} sp,efirst=t:match("^( ?)(%p?%a[%w%s]+%a)") if efirst then table.insert(ttab,efirst) t=t:gsub("^ ?%p?%a[%w%s]+%a","") end for ar,en in t:gmatch(" ?(.-) (%p?%a[%w%s]+%a)") do ar=ar:gsub("(.*)%.%.%.","...%1"):gsub("(.*)([%.,%?!:;])","%2%1"):gsub("(.*)","%1") table.insert(ttab,ar) table.insert(ttab,en) end alast=t:match(".*%a[%w%s]+%a (%A-)$") if alast then alast=alast:gsub("(.*)%.%.%.","...%1"):gsub("(.*)([%.,%?!:;])","%2%1"):gsub("(.*)","%1") table.insert(ttab,alast) end if #ttab==0 then return t end nt="" for a=1,#ttab do nt=" "..ttab[a]..nt end t=nt return t end tags1={"blur","be","bord","shad","xbord","xshad","ybord","yshad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay" } tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} tags3={"pos","move","org","fad"} function duplikill(tagz) tagz=tagz:gsub("\\t%b()",function(t) return t:gsub("\\","|") end) for i=1,#tags1 do tag=tags1[i] repeat tagz,c=tagz:gsub("|"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%2%1") until c==0 end tagz=tagz:gsub("\\1c&","\\c&") for i=1,#tags2 do tag=tags2[i] repeat tagz,c=tagz:gsub("|"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 repeat tagz,c=tagz:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%2%1") until c==0 end repeat tagz,c=tagz:gsub("\\fn[^\\}]+([^}]-)(\\fn[^\\}]+)","%2%1") until c==0 repeat tagz,c=tagz:gsub("(\\i?clip%b())(.-)(\\i?clip%b())", function(a,b,c) if a:match("m") and c:match("m") or not a:match("m") and not c:match("m") then return b..c else return a..b..c end end) until c==0 tagz=tagz:gsub("|","\\"):gsub("\\t%([^\\/%)]-%)","") return tagz end function styleget(subs) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i]) end if subs[i].class=="dialogue" then break end end end function stylechk(stylename) for i=1,#styles do if stylename==styles[i].name then styleref=styles[i] if styles[i].name=="Default" then defaref=styles[i] end break end end return styleref end function addtag(tag,text) text=text:gsub("^({\\[^}]-)}","%1"..tag.."}") return text end function logg(m) aegisub.log("\n "..m) end function fixpunct(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text tags=text:match("^{\\[^}]-}") or "" text=text:gsub("^{\\[^}]-}","") :gsub("^(.*)(%.%.%.)$","%2%1") :gsub("^(%.%.%.)(.-)\\N","%2\\N%1") :gsub("^(.*)([%.,%?!:;])$","%2%1") :gsub("^([%.,%?!:;])(.-)\\N","%2\\N%1") :gsub("^(.*)—$","—%1") :gsub("^—(.-)\\N","%1\\N—") :gsub("^(.*)$","%1") :gsub("^(.-)\\N","%1\\N") text=tags..text line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro("Aladin's Lamp/Make Djinn fix inline tags",script_description,fixnline) aegisub.register_macro("Aladin's Lamp/Make Djinn fix line breaks",script_description,fixbreak) aegisub.register_macro("Aladin's Lamp/Make Djinn fix punctuation",script_description,fixpunct) aegisub.register_macro("Aladin's Lamp/Make Djinn fix English text",script_description,fixeng) --[[

Muxing script version 1.1 Read everything here before using it.

DISCLAIMER

You are receiving this badly written piece of code for several reasons:

1. I can't write good code 2. I don't have time to learn to write good code 3. I don't particularly care about good code, as long as my bad code works 4. The people who can write good code spend more time criticizing my code than writing their own 5. I have somehow accidentally become the main lua-code-writer in fansubbing (hell knows why), so you don't have much choice

Conclusion: deal with it.

TERMS AND CONDITIONS

1. You may NOT use this software if

- You are autistic about badly written code. This includes complaining about things written in a way you don't like, even though they work, spending hours criticizing bad code instead of writing a better one, complaining about code you're not even using, and so on.

- You are a citizen of the United States of America or Israel. We do not support ZioNazi countries that routinely break International Law, murder innocent civilians by thousands, use their police/military to beat up elderly people, taser children to death, or shoot whole families while raiding the wrong house, invade other countries on false premises, organize or support coup d'etats in other countries, support dictators worldwide, create terrorist organizations and then tell us we have to fight them, engage routinely in false-flag terrorism, feed us daily with lies about Saddam's nonexistent WMDs, Hamas's rockets that are either nonexistent or fired by MOSSAD agents, and other similar things, and generally strive every day to fuck up the whole planet.

- You work for Monsanto or another rabidly insane corporation hell-bent on destroying the Earth.

- You are under 30 years of age. Kids shouldn't play with badly written code.

2. You are NOT allowed to

- Steal any part of this code, because it's bad for you to use bad code.

- Modify this code in any way, because modifying bad code is like playing with grenades.

- Sell this code, because selling bad code equals terrorism!

3. You ARE allowed to

- Use this code to do what it's designed to do (see USAGE section), as long as conditions 1 and 2 are met.

- Report real bugs, as in when the software isn't correctly doing what it claims to be able to do.

- Ask for new features, as long as such features make sense within the scope of what this software does.

- Read the comments in this file and learn something about writing lua scripts for Aegisub.

- Use the software beyond its limitations (see point 4) ONLY AT YOUR OWN RISK. I will not be responsible for any damage you cause that way.

- Write better code.

- Do all kinds of things that have no relation to this software whatsoever, like bungee jumping.

4. Limitations:

This software has a (probably large) number of limitations, both documented and undocumented. Documented ones are as follows:

- This software is designed to work on Windows, even though Windows is shit, because that's the only OS I have and can work with. As such, what the software will do on other OSs is completely unpredictable to me, thus it may or may not work as intended, and may possibly harm your computer or do even worse things, like stab you in the eye or end the world.

- Unicode characters in file/folder names. Running lua from within Aegisub has various limitations compared to running it just under Windows, especially with regards to messing with files and folders. I don't possess extensive knowledge on these matters, but my personal experience shows that unicode characters in file/folder names is a likely factor to make things not work, without it being very clear why exactly it is that they are not working. Therefore it is recommended that folder names and file names do not contain any such characters.

- External programs (without which this software's functionality is limited or impossible)

I. Mkvmerge. Mkvmerge is absolutely necessary for this software to do anything of use, i.e. muxing, its primary purpose. Mkvmerge must be installed on your computer and a path to it given in the top field of this software's GUI. Note that the file required is mkvmerge.exe and not mmg.exe.

II. Enfis_SFV.exe This is required if you want your muxed mkv file to automatically contain its CRC in the filename. If you don't need this feature, Enfis is not necessary.

III. xdelta If you wish to create an xdelta file that patches your source video to the muxed video, you need to download xdelta3.exe and input its location in the GUI. Whether you call it xdelta3.exe or xdelta.exe or whatever else is unimportant, as long as it's the correct file and the file path/name is correct.

IV. Lua for Windows This is not required if you don't need CRC/xdelta but is necessary if you want to use those. The reason is that the code that creates the CRC/xdelta can only be run after a video file is muxed and thus is run outside of Aegisub. As the only language I can write this in is lua, it requires Lua for Windows to run. You can download this for free from the Internet. Note: It would be possible to do everything from Aegisub if run right away, but I wanted the whole process to be executable from a single batch file that you can run later.

- You can not change the contents of the source video, i.e. whatever streams it already has will be used.

- You can mux only two subtitle files.

- You can only set track name and language for subtitle files.

- You have to set the language code manually, thus you have to know what the correct code is. You can't just type 'english' and think it will work. You can check these codes in mkvmerge. The correct for English is 'eng (English)'. You can use either of those two - 'eng' or 'English'. But there is a difference between 'English' and 'english', the latter being invalid.

- Detecting muxed video name and episode number This process is currently very limited and not very likely to be significantly improved, unless unexpectedly good suggestions arise. This name and number are used for two things: - to try to determine the muxed encode's name - to locate fonts folder if set to "script folder/ep number" or "video folder/ep number" There are two places to look for these variables: - (primary) Script title (in Properties in Aegisub) - (secondary) script filename (.ass file) If at least one of these contains the show's name and episode number, things should work well, assuming the number is the last thing in the name. If no number is found, the whole string is the name, and episode number is an empty string. (Thus "script folder/ep number" should become the same as "script folder".) If for whatever reason this process doesn't produce the desired results, you have to type Muxed video name manually. This is important when creating an xdelta file, as it will contain the given filename, and if you rename the muxed file later, the patch will not work!

Other limitations are possible, even likely. If you find such limitations and think they could reasonably be removed, report them.

USAGE

1. To ensure correct usage, read TERMS AND CONDITIONS first.

2. What this script can do.

This script should be able, fairly easily, to mux a video and subtitle file you have loaded in your Aegisub. It can mux a secondary subtitle file that you select. It can also mux a selected xml file with chapters. When all appropriate conditions are met, it can add correct CRC to the muxed file's name. When even more conditions are met, it can create an xdelta file to patch the source video to the muxed one. It can also, for various reasons, fail to do the things just described. It can possibly drive you mad by repeated failures for reasons you're desperately trying to ascertain but which inexplicably keep eluding you. It can occasionally fail to even load because you're using a shitty browser called Chrome, which likes to add, for reasons unclear, html tags to downloaded lua files. It can lock your Aegisub for a while if you're muxing a huge video file. It can most likely do other unspecified things that no one has discovered yet. Those are, however, not intentional.

3. Explanation of the GUI.

- mkvmerge.exe This is an essential part of this software. You can't mux without this file. Use the mkvmerge button at the bottom and navigate to where your mkvmerge.exe is. To make the GUI remember the path, use the 'Save settings' button.

- Fonts folder This is where the script will look for fonts that are to be muxed. The presets are for the script folder and video folder (which may be the same). The option with '/fonts' means the fonts should be in a folder called 'fonts' in the script/video folder. The option with '/ep number' means that instead of 'fonts', the folder will be called for example '01'. This number is determined as described in TERMS AND CONDITIONS under '4. Limitations', section 'Detecting muxed video name and episode number'. If you use 'custom path:', use the 'fonts' button at the bottom, navigate to the folder where your fonts are, and select one of the fonts. (Aegisub doesn't allow selecting just folders. The filename will be removed.)

- Group tag Here you can set your group's tag, which will be automatically used at the beginning of the muxed video's name. Example: [TerribleSubs]

- Enfis_SFV.exe This is needed only when creating CRC (which is needed when making an xdelta file.) It's an external binary which you can download for example here: http://unanimated.xtreemhost.com/SFV_Checker.zip

- xdelta(3).exe This is needed only when creating an xdelta patch. Easily downloadable from the Internet. It doesn't matter whether the name is xdelta.exe or xdelta3.exe as long as the path leads to the correct file.

- Muxed video name: The script tries to build this name from information it collects. Again, see section 'Detecting muxed video name and episode number' for more details. the basic pattern is: 'GroupTag Name - EpNumber.mkv' Depending on how you name your script's Title and filename, this may be more or less useful. Adjust the filename manually as needed. When using CRC, the CRC is inserted after the episode number automatically after muxing, with the usual pattern.

- Source video This is the video to be used for muxing and should be the video you have loaded in Aegisub. Obviously, trying to mux without video loaded will result in failure.

- -A/-S/-M/nc/nt Options for source video. (Disable audio, subs, attachments, chapters, tags.)

- File/segment title Same as in mkvmerge, this is what's displayed in medianfo and players as the 'Movie name'.

- Video options Additional options for source video. May include --track-name etc.

- Subtitle 1 This is the subtitle file to be used for muxing and should be the subtitle file you have loaded in Aegisub. Obviously, trying to mux without the correct subtitle file loaded will result in failure.

- Set as default This is useful when the input video already has subtitles and you want your subs to be the default ones.

- Subtitle 1 mkv title This is the title of the subtitles that you can see in your player when selecting a subtitle stream. This field is optional.

- Language This is the language displayed in your player for this subtitle stream. This field is optional.

- Subtitle 2 You can use one instance of alternative subtitles, for example with/without honorifics, or another language. Use the 'Subs 2' button to locate the file (or paste a correct path).

- Subtitle 2 mkv title + language Same as for the first one; optional.

- Chapters If you need to mux chapters, use the Chapters button to locate the xml file. If it exists in the same folder as your subtitle file and has the same naming pattern, it will be selected automatically. It will, however, only be muxed if the checkbox is checked.

- Create CRC Use this if you want the CRC in the filename of the muxed file. This requires Enfis_SFV.exe as explained above, as well as Lua for Windows.

- Create xdelta patch Use this if you want an xdelta file that patches the source video (premux, workraw) to the muxed file. This requires Enfis_SFV.exe, xdelta3.exe, and Lua for Windows. You can't make an xdelta without creating CRC.

- Delete temporary files when done The whole process creates a bunch of files required for everything to work. These normally serve no purpose when everything works and can and should be deleted afterwards. If, however, the process fails at some point, it's useful to keep the files to determine what exactly failed.

- Keep cmd window open This, again, is not necessary, and in fact rather useless when everything works, but can be helpful when the muxing fails, as the cmd window will stay open and you should be able to see which part of the process failed.

- Buttons: Mux: Creates files needed for muxing. (Muxing itself has to be confirmed in a later dialog.) mkvmerge: lets you select mkvmerge.exe on your HD. fonts: lets you select a folder containing fonts for muxing (though you need to select one of the files). Enfis: lets you select Enfis_SFV.exe on your HD. xdelta: lets you select xdelta3.exe on your HD. Subs 2: lets you select an alternative subtitle file. Chapters: lets you select chapters file (xml). Save settings: Saves settings - top 5 lines of GUI (except custom path), languages for subs, and bottom 4 checkboxes. Cancel: Casts level 3 Invisibility on your GUI. (Lasts until summoned again.)

4. The Muxing Process.

When you set up everything correctly, click on the Mux button. A cmd window will flash and close. This part of the process creates the necessary files for muxing. Then a dialog will pop up (assuming there was no error) that will list: - Files to mux (video, subtitles) - Number of fonts to mux that the script found - The final filename of the muxed mkv - Location of 'muxing.bat' (should be same as video's location)

In case of any problems, or during first tests, it's useful to check these statistics.

Make sure the video and subtitle files are correct. The number of fonts should give you an idea if it found all of them. (Only ttf and otf are supported.) Make sure the muxed file's name is correct, especially if doing CRC. When doing CRC, the filename will contain [CRC], because the actual CRC hasn't been created yet; it will be replaced later.

'muxing.bat' is what executes the whole muxing process, including CRC and xdelta if selected.

If you click Yes, the muxing starts immediately. If you click No, you can run 'muxing.bat' later.

Running things from Windows may in some cases have more permissions than running things from Aegisub, so choosing No and running the batch file from Windows may have higher rate of success. (I don't really know the specifics, though, so don't quote me on that.)

What happens when you start muxing:

- A batch file (mux.bat) in the fonts folder muxes the video + subtitles + fonts. This should have a relatively low chance to fail, compared to the later parts. If you don't do CRC, this is all that needs to be done.

- CRC creation / xdelta Enfis_SFV.exe is called to create a sfv file with the CRC. sfv.lua (in video folder) creates 'patchrel.bat' and possibly 'xdbatch.bat' (if making xdelta). Creating xdelta without CRC is not allowed. patchrel adds the correct CRC to the muxed video's name. xdbatch creates the xdelta file. Note: sfv.lua could do the renaming and patching right away, but i hate running os.execute from lua, because it's a pain in the ass and causes more problems than anything else, so i prefer those bat files.

5. Predictable Problems and Possible Solutions.

It's probably quite likely that the first try won't work, as there are plenty of things that can go wrong. Several basic things to do in such a case would be: I. Make sure all the filenames and file paths are correct, that all the needed files actually exist in the right place. II. Check 'Keep cmd window open'. If you get to muxing at least, the cmd window should tell you what part failed. III. uncheck 'Delete temporary files when done'. While the cmd window will tell you what part of the process failed, it may not clearly tell you why. Find which of the temp files it failed at, and try to execute that file under Windows. You should open a new cmd window first and navigate to the folder, so that the window stays open and shows you the error. This is especially the case with sfv.lua. If the problem is with that one, running it from a cmd window under Windows will tell you which line it failed at and why. IV. If the process fails with CRC or xdelta, try the same without CRC first to see if that part works. This narrows down where the error occurred.

The problems are often with files or folders not found, which may be because they were incorrectly specified, they had unicode characters in them, possibly some characters that have a function in batch files (I only work around '=' in folder names; not sure what others are a problem), or for other reasons(?). It can be useful to test in a simply named folder in the root, like 'D:\sub', to eliminate naming problems.

If you start muxing and muxing actually occurs, you see the progress of muxing in the cmd window. This takes a while, depending on the size of the video. If the cmd window disappears within about 5 seconds and the process fails, it's probably the muxing that failed. If you keep the cmd window open and scroll up to the muxing part, you should see the relevant error.

Make sure you have Lua for Windows installed (for CRC), and that lua files are associated with it! If this isn't the case, it may be hard to detect what's failing.

ttc (and other, even weirder types of) fonts don't get muxed. Don't use them.

6. Tips and Recommendations.

- Don't use unicode characters anywhere in the process.

- For smooth automatic naming, use this pattern in the script's Title: 'Show's name 01' or 'Show's name - 01', where 01 is the episode number. The Title is ignored if it's empty or the default, in which case the .ass filename is used. Generally speaking, just use the 'name number' pattern with space, ' - ', or nothing in between.

- At least until you get things to work reliably, test with mkvextract or mmg if all fonts are actually in the muxed mkv.

7. List of external software used

mkvmerge - http://www.videohelp.com/tools/MKVtoolnix Lua for Windows - http://code.google.com/p/luaforwindows/downloads/list Enfis_SFV - http://www.softpedia.com/get/System/File-Management/Command-Line-SFV- Checker.shtml#download or http://unanimated.xtreemhost.com/SFV_Checker.zip xdelta - http://code.google.com/p/xdelta/downloads/list

mkvmerge is the only essential one. Others only for advanced functions.

--]]

-- Everything in this script is extensively documented for educational and possibly entertainment purposes.

-- Here's where you learn about the generic name, boring description, notorious author, and current version of this abominable software. script_name="Multiplexer" script_description="Muxes stuff using mkvmerge" script_author="unanimated" script_version="1.1" script_namespace="ua.Multiplexer" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="1.1.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub- Scripts/master/DependencyControl.json"} end

-- Here's where the actual script starts, though that's not really true because in a way it starts at the top, -- and from a technical point of view it starts at the last line, which then redirects here. -- Let's just settle on the idea that the main function starts here, though it might still be debated which function is the main one. function mux(subs,sel) -- This makes commonly used things shorter. ADD=aegisub.dialog.display ADP=aegisub.decode_path ak=aegisub.cancel

-- This is where your settings are stored, assuming you saved them there. -- In case you didn't know, '?user' is the folder of your Application Data, which differs based on your OS. muxconfig=ADP("?user").."\\mux-config.conf"

-- This is where your video is located, assuming you have one loaded. If you don't, you're doing it wrong. vpath=ADP("?video").."\\"

-- This is where your currently loaded subtitle file (.ass) is located. -- It better be the one you want to mux because if it isn't, you're doing it even wronger. spath=ADP("?script").."\\"

-- This is the filename of your ass. Yes, that's a bad pun. -- Somehow when dealing with Aegisub, you always get bad ass puns. That was a pun too. See what I mean? scriptname=aegisub.file_name()

-- This erases any possible videoname remaining from the last run of the script. -- You see, I write terrible code (5 of 4 'experts' says so, so it's clearly true), -- so I use global variables everywhere, because I don't really like the local ones. -- Global ones seem so much more practical. They're supposed to be slower, -- but it's not like I'm computing an intergalactic journey for a spaceship here. -- So occasionally, I have to erase some of these global variables so that they don't cause confusion. videoname=nil

-- This attempts to translate the subtitle filename into the show's name and episode number. -- This of course assumes you're working on an episode of a show, which easily may not be the case. -- However, as this is mostly intended for people who do work on such things, it is fairly likely that this might work. -- If it doesn't, well, shit happens. You'll just get bad default naming. -- In the end, it's probably your fault because you had shitty filenaming in the first place. show,ep=scriptname:match("^(.-)%s*(%d+)%.ass")

-- Here, if you don't have a number in the filename, we try to check if maybe you have OVA in the name. -- This would tell us that you're not working on a regular episode, but an OVA, in which case we use 'OVA' instead of the episode number. -- Now, it's marginally possible that you named your file in all caps, and it ends with 'OVA', like 'ANNA KOURNIKOVA'. -- In such a case we apologize for the unexpected results and politely ask, "What the fuck are you subbing?" -- If 'OVA' isn't detected either, we'll just take the whole name as is (without the .ass), and to hell with the number. if ep==nil then show,ep=scriptname:match("^(.-)%s*(OVA)$") if ep==nil then show=scriptname:gsub("%.ass","") ep="" end end

-- Here we try to read your saved settings, assuming you saved any. -- It is most likely to succeed if you did, unless you were dumb enough to fuck with the saved file and did something wrong with it. -- Should that be the case, you're an idiot, and things will break. Don't fuck with things you don't understand. file=io.open(muxconfig) if file~=nil then -- This is the part where the file with the settings actually exists (though it's not clear yet what's really in it). konf=file:read("*all") io.close(file) mmgpath=konf:match("mmgpath:(.-)\n") fontpath=konf:match("ffpath:(.-)\n") tag=konf:match("tag:(.-)\n") sfvpath=konf:match("enfis:(.-)\n") xdpath=konf:match("xdelta:(.-)\n") lang1=konf:match("lang1:(.-)\n") lang2=konf:match("lang2:(.-)\n") crc=detf(konf:match("crc:(.-)\n")) patch=detf(konf:match("patch:(.-)\n")) delete=detf(konf:match("delete:(.-)\n")) cmdopen=detf(konf:match("cmdopen:(.-)\n")) else -- This is the part where it doesn't exist, so we create some default values. mmgpath="" fontpath="custom path:" tag="[SomeShitGroup]" sfvpath="download from http://unanimated.xtreemhost.com/SFV_Checker.zip or elsewhere" xdpath="" lang1="eng" lang2="" crc=false patch=false delete=true cmdopen=false end

-- This is the part where we check the info section of your ass and look for the name of your video file and script title. for i=1,#subs do l=subs[i] if l.class=="info" then -- Here we found the info part, so we look for those other things. -- Finding the info part is rather easy, by the way, as it's always at the beginning. -- At least that's the latest theory. if l.key=="Video File" then videoname=l.value end if l.key=="Title" then title=l.value end end -- This is where we break this process because the party's over. if l.class~="info" then break end end

-- Now, if video file wasn't found, nothing's lost yet! -- In fact, it would only be found with an older version of Aegisub, which I hope you're not using, because that would be lame. -- So now, in a newer version, we get the video name from this thing called project_properties. -- If we don't find that either, it means you don't have any video loaded, in which case... -- Well, in which case things break and we don't really give a fuck, because you should have loaded it. -- How the hell do you wanna mux without a video? You wanna make a fucking mks? Nigger pls. if videoname==nil then videoname=aegisub.project_properties().video_file:gsub("^.*\\","") end

-- Now we have a title, so we try to get an episode number from it if possible. -- Or maybe we don't have a title because we just got an empty string. -- Or maybe we just have "Default Aegisub file", which is just as useless. -- Anyway, we use what's useful and ignore what's useless, and in the latter case revert to using the filename. if title==nil or title=="Default Aegisub file" then title=show end if title:match("%d+$") then ep=title:match("(%d+)$") title=title:gsub("%s?%-?%s?%d+$","") end

-- Here we compile the name for the muxed file from what we have. -- We use the group tag, that is if you already have one saved. -- Then we take the name we dug out of the title or filename, and attach the episode number if we have found one. -- If what we found was some stupid shit, it's because you name your things stupidly, so balme yourself for the result. if tag~="" then tag2=tag.." " else tag2="" end if ep~="" then ep2=" - "..ep else ep2="" end mvideo=tag2..title..ep2..".mkv"

-- Should it somehow happen that the name ends up the same as your source video, we ad '_muxed' to avoid an obvious fuckup. -- By the way, did you ever notice that the word 'fuckup' looks like something you could put on your french fries? Weird. if mvideo==videoname then mvideo=mvideo:gsub("%.mkv","_muxed.mkv") end

-- Here we make a leap of faith and check if by any chance you have a chapter file in the same folder as your subtitle file, -- and if by any chance you named it the same (save for the extension). -- Should that be the case, you're cool, and we automatically list this file in the GUI to save you the trouble of looking for it. ch_name=spath..scriptname:gsub("%.ass",".xml") file=io.open(ch_name) if file==nil then ch_name="" else file:close() end

-- Now we build that mostrous thing called GUI with all those fields and checkboxes and buttons. GUI={ -- First the part where you select the things that don't change (unless you weird and change them every time). -- Most of the things in this block are taken from your saved settings (if you saved them). {x=0,y=0,class="label",label="mkvmerge.exe:"}, {x=1,y=0,width=11,class="edit",name="mmgpath",value=mmgpath}, {x=0,y=1,class="label",label="Fonts folder:"}, {x=1,y=1,width=2,class="dropdown",name="ff",value=fontpath,items={"script folder","script folder/fonts","script folder/ep number","video folder","video folder/fonts","video folder/ep number","custom path:"}}, {x=3,y=1,width=9,class="edit",name="fontspath",value=fontspath or "(only custom path goes here)"}, {x=0,y=2,class="label",label="Group tag:"}, {x=1,y=2,width=11,class="edit",name="tag",value=tag}, {x=0,y=3,class="label",label="Enfis_SFV.exe:"}, {x=1,y=3,width=11,class="edit",name="enfis",value=sfvpath}, {x=0,y=4,class="label",label="xdelta(3).exe:"}, {x=1,y=4,width=11,class="edit",name="xdelta",value=xdpath},

-- This is just some writing, mostly me repeating the same things again because users can be pretty dumb. {x=1,y=5,width=7,class="label",label="'Save settings' saves the above + languages + the bottom 4 checkboxes."},

-- Here's video stuff {x=0,y=6,class="label",label="Muxed video name:"}, {x=1,y=6,width=11,class="edit",name="mvid",value=mvideo}, {x=0,y=7,class="label",label="Source video:"}, {x=1,y=7,width=6,class="edit",name="vid",value=videoname}, {x=7,y=7,class="checkbox",name="noA",label="-A ",hint="no audio"}, {x=8,y=7,class="checkbox",name="noS",label="-S",hint="no subtitles"}, {x=9,y=7,class="checkbox",name="noM",label="-M ",hint="no attachments"}, {x=10,y=7,class="checkbox",name="noC",label="nc",hint="no chapters"}, {x=11,y=7,class="checkbox",name="noT",label="nt",hint="no tags"}, {x=0,y=8,class="label",label="File/segment title:"}, {x=1,y=8,width=4,class="edit",name="vtitle"}, {x=5,y=8,width=3,class="checkbox",name="VO",label="Video options:",hint="additional input video options"}, {x=8,y=8,width=4,class="edit",name="vopt"},

-- Here's primary subtitle stuff {x=0,y=9,class="label",label="Subtitle 1:"}, {x=1,y=9,width=8,class="edit",name="subs",value=spath..scriptname}, {x=9,y=9,width=3,class="checkbox",name="defsub",label="Set as default",hint="You can use this when orginal video already has subs"}, {x=0,y=10,class="label",label="Subtitle 1 title:"},{x=1,y=10,width=6,class="edit",name="subname1",value=""}, {x=7,y=10,width=2,class="label",label=" Language: "},{x=9,y=10,width=3,class="edit",name="lang1",value=lang1 or ""},

-- Here's secondary subtitle stuff {x=0,y=11,class="checkbox",name="sub2",label="Subtitle 2:"},{x=1,y=11,width=11,class="edit",name="subs2",value=""}, {x=0,y=12,class="label",label="Subtitle 2 title:"},{x=1,y=12,width=6,class="edit",name="subname2",value=""}, {x=7,y=12,width=2,class="label",label=" Language: "},{x=9,y=12,width=3,class="edit",name="lang2",value=lang2 or ""},

-- And here's chapter stuff {x=0,y=13,class="checkbox",name="ch",label="Chapters",value=false}, {x=1,y=13,width=11,class="edit",name="chapters",value=ch_name},

-- This block is checkboxes with additional options {x=0,y=14,class="checkbox",name="sfv",label="Create CRC",value=crc,hint="requires Enfis_SFV.exe and lua for windows"}, {x=1,y=14,class="checkbox",name="xd",label="Create xdelta patch",value=patch,hint="requires Enfis_SFV.exe, xdelta3.exe, lua for win"}, {x=3,y=14,width=2,class="checkbox",name="del",label="Delete temporary files when done ",value=delete}, {x=5,y=14,width=4,class="checkbox",name="cmd",label="Keep cmd window open",value=cmdopen},

-- This shows the user what version of this software this is {x=9,y=14,width=3,class="label",label=" [ Multiplexer v"..script_version.." ]"}, }

-- This is where we attach a function to most of the buttons in the GUI. -- The function opens a dialog and lets you browse your HD and find and select the file required. -- It shows you what it is you need to find, in case your attention span is really fucking short. -- It also mostly only lets you select files with the right extension, in case you're an idiot. -- Once you select stuff, it adds it to the appropriate place in the GUI, while keeping the other stuff unchanged. repeat if P=="mkvmerge" then mmg_path=aegisub.dialog.open("mkvmerge.exe","",spath,"*.exe",false,true) gui("mmgpath",mmg_path) end if P=="fonts" then ff_path=aegisub.dialog.open("Fonts folder (Select any file in it)","",spath,"",false,true) if ff_path then ff_path=ff_path:gsub("\\[%w%s]+%.%w+$","\\") end gui("fontspath",ff_path) end if P=="Enfis" then sfv_path=aegisub.dialog.open("Enfis_SFV.exe","",spath,"*.exe",false,true) gui("enfis",sfv_path) end if P=="xdelta" then xd_path=aegisub.dialog.open("xdelta(3).exe","",spath,"*.exe",false,true) gui("xdelta",xd_path) end if P=="Subs 2" then s2_path=aegisub.dialog.open("Secondary subtitle file","",spath,"*.ass",false,true) gui("subs2",s2_path) end if P=="Chapters" then ch_path=aegisub.dialog.open("Chapters","",spath,"*.xml",false,true) gui("chapters",ch_path) end

-- This is a rather important part, because it saves your settings. -- Without this, you'd have to input everything every time, which would be pretty damn annoying. -- So thank some ancient deities that I know how to do this, because if I didn't, nobody else would probably do it. if P=="Save settings" then

-- These 4 lines use a cool function that converts boolean values to text. -- OK, maybe it's not really that cool. Whatever. Shut up. kcrc=tf(res.sfv) kxdel=tf(res.xd) kdel=tf(res.del) kcmd=tf(res.cmd)

-- This is where a list of settings to save and their current values is written. konf="mmgpath:"..res.mmgpath.."\nffpath:"..res.ff.."\ntag:"..res.tag.."\nenfis:"..res.enfis.."\nxdelta:"..res.xdelta.."\nlang1 :"..res.lang1.."\nlang2:"..res.lang2.."\ncrc:"..kcrc.."\npatch:"..kxdel.."\ndelete:"..kdel.."\ncmdopen:"..kcmd.."\n"

-- Now we create the file (in the place we specified at the beginning of this whole function). file=io.open(muxconfig,"w") file:write(konf) file:close()

-- We need to make sure that any changes we made in the GUI don't get reverted, so we apply the current values. for k,v in ipairs(GUI) do v.value=res[v.name] end

-- This lets you know that your settings were saved, and where. -- It's good to do this for 2 reasons: -- 1. You know that something actually happened, and was successful. -- 2. You know where the settings are, in case you ever need it. (But don't fuck with it if you don't know what you're doing.) ADD({{class="label",label="Settings saved to:\n"..muxconfig}},{"OK"},{close='OK'}) end

-- This is the part where we actually build the GUI. -- You may ask, "Wait! WTF? How are we only building it now when we were messing with it for a while?" -- Well, that's a good question. The not-too-long answer is something like this: -- All we've done so far with the GUI is inside a 'repeat' loop that displays the GUI again each time a button activates a function. -- So the first time, with no button P yet, it ran all the way here to display the GUI. -- Then, after pressing buttons, it does the stuff above, comes back here, and displays it again. P,res=ADD(GUI,{"Mux","mkvmerge","fonts","Enfis","xdelta","Subs 2","Chapters","Save settings","Cancel"},{ok='Mux',close='Cancel'})

-- This is what breaks the repeat loop. Either it's Cancel, which gives you cancer... (better not click that) -- or it's Mux, which says, "OK, I'm done with this fucking clicking around in this stupid GUI. Let's have some action!" until P=="Mux" or P=="Cancel"

-- This is where you get cancer. if P=="Cancel" then ak() end

-- If you get here, it means you didn't get cancer (that's good news!) and that files for muxing are being prepared, -- of which, as you can see, the user is properly being informed, because if there's one thing worse than cancer, it's uninformed public. aegisub.progress.title("Preparing files for muxing...")

-- Here we take some results from the GUI and give them an easier-to-use name because it's easier to use. -- It is mainly done with the ones that get referenced later multiple times. If it's used only once, no need to bother. video=res.vid mvideo=res.mvid subname1=res.subname1 subname2=res.subname2 lang1=res.lang1 lang2=res.lang2 fontspath=res.fontspath if res.vtitle~="" then vtitle=" --title "..quo(res.vtitle) else vtitle="" end

-- Here we anticipate the possibility that you didn't read the fucking instructions and are doing something stupid, -- namely trying to create an xdelta without creating CRC when we clearly said it's not allowed. -- So if you check xdelta and not CRC, we check CRC for you. if res.xd then res.sfv=true end

-- It has come to our attention that some people will select mmg.exe instead of mkvmerge.exe, -- so this is where we tell them they're doing it wrong. if res.mmgpath:match("mmg.exe") then t_error("ERROR: Youre doing it wrong.\n'mmg.exe' is not 'mkvmerge.exe'.",true) end

-- In case you still got a wrong file anyway, here we tell you so to save you one "Why isn't this working?" moment. if not res.mmgpath:match("mkvmerge.exe") then t_error("ERROR:\n'"..res.mmgpath.."' is not a valid 'mkvmerge.exe' file.",true) end

-- This is the part where we write a script that gets the CRC, renames the muxed mkv file, and creates an xdelta if res.sfv then

-- First we check if you actually input the path to Enfis_SFV.exe. -- If you didn't, we decide to proceed without creating CRC. if res.enfis=="" then t_error("Enfis_SFV.exe not specified.\nProceeding without CRC.") bat_crc="" else -- Here we do another check, to see if Enfis_SFV.exe that you point to actually exists. -- If it doesn't, mission failed. -- It will not fail, however, at least not at this point, if you pointed to a different file that exists. -- In such a case operations would proceed, but the CRC would not be created because you're a douchebag. file=io.open(res.enfis) if file==nil then t_error("FILE NOT FOUND!\n"..res.enfis,true) else file:close() end

-- If you successfully made it this far, we insert '[CRC]' in the filename. -- This will later be replaced with the actual CRC, once we know what it is. mvideo=mvideo:gsub("%.mkv"," [CRC].mkv")

-- This is the command line that creates a file with the CRC, which will be run from 'muxing.bat'. bat_crc=quo(res.enfis).." -f=\"whatisthisidonteven.sfv\" "..quo(mvideo).."\ncall sfv.lua\ncall patchrel\ncall xdbatch\n"

-- This is a pain-in-the-ass part of the code that creates sfv.lua. -- Getting all these quotation marks and escape slashes right is fucking hell. -- Anyway, this sfv.lua is what will be reading the sfv file with the CRC -- and creating batch scripts for renaming the mkv and creating an xdelta. luavpath=vpath:gsub("\\","\\\\") sfvlua="file=io.open(\""..luavpath.."whatisthisidonteven.sfv\")\nsfvtext=file:read(\"*all\")\nfile:close()\ncrc=sfvtext:mat ch(\"%.mkv%s(%x+)\")\nvideo=\""..mvideo.."\"\ncrc_name=video:gsub(\"%[CRC%]\",\"[\"..crc..\"]\")\ncrctext=\"rena me \\\""..mvideo.."\\\" \\\"\"..crc_name..\"\\\"\"\nfile=io.open(\""..luavpath.."patchrel.bat\",\"w\")\nfile:write(crctext)\nfile:close()\n"

-- Here's the (optional) xdelta part. if res.xd then -- Again, we check if the given xdelta3.exe exists, because we know some of you are morons, -- and in absence of said file we politely inform you that there will be no patching for reasons just explained. file=io.open(res.xdelta) if file==nil then t_error("FILE NOT FOUND!\n"..res.xdelta.."\nProceeding without patching.") xdtext="" else -- This is the xdelta part of the pain-in-the-ass sfv.lua code, and will be attached if making xdelta was selected. luaxd=res.xdelta:gsub("\\","\\\\") xdt="xdtext=\"call \\\""..luaxd.."\\\" -f -s \\\""..video.."\\\" \\\"\"..crc_name..\"\\\" \\\""..show..ep..".xdelta\\\"\"\nfile=io.open(\""..luavpath.."xdbatch.bat\",\"w\")\nfile:write(xdtext)\nfile:close()" end else -- If xdelta wasn't selected, we give this empty string a few lines later, and we delete patching from the batch. xdt="" bat_crc=bat_crc:gsub("\ncall xdbatch","") end

-- Proceeding to write sfv.lua, consisting of the CRC part and xdelta part that, as previously mentioned, -- may be an empty string in the case of making xdelta not being selected. file=io.open(vpath.."sfv.lua","w") file:write(sfvlua..xdt) file:close()

end else -- If we're not making CRC, an empty string will be supplied to the main batch file. (In other words basically nothing.) bat_crc="" end

-- This simple part of the code defines what the path to fonts-to-mux is based on given settings. -- It's so simple that even you can write it. Or at least understand it, I'm sure! if res.ff=="script folder" then ffpath=spath end if res.ff=="script folder/fonts" then ffpath=spath.."fonts" end if res.ff=="script folder/ep number" then ffpath=spath..ep end if res.ff=="video folder" then ffpath=vpath end if res.ff=="video folder/fonts" then ffpath=vpath.."fonts" end if res.ff=="video folder/ep number" then ffpath=vpath..ep end if res.ff=="custom path:" then ffpath=res.fontspath end -- Here we add a backslash at the end, so that we don't have to do it multiple times later. ffpath=ffpath.."\\"

-- This determines what to do based on whether the user wishes to mux a chapters file. if res.ch then -- If we're muxing chapters, this string will be added to the muxing batch file. bat_chap="--chapters "..quo(res.chapters).." "

-- Again check if chapters file actually exists. -- (Yeah, this is rather boring and repetitive, but people tend to be repetitively stupid, -- and constant checking of everything saves them from a lot of the bad kind of errors. -- This way, they get the good kind of error, that is to say, one that tells them clearly what's wrong.) -- If chapters don't exist where they should, the process proceeds without them. file=io.open(res.chapters) if file==nil then t_error("Chapters not found:\n"..res.chapters.."\nProceeding without.") bat_chap="" else file:close() end else -- If chapters aren't selected for muxing, empty string goes to final batch file. bat_chap="" end

-- Here we infiltrate the fonts folder and secretly plant a file there. -- This file will shortly be used to get the filenames of fonts. list="cd /d "..quo(ffpath).."\ndir /b>files.txt\ndel list.bat" file=io.open(ffpath.."list.bat","w") file:write(list) file:close()

-- Now we use that file we just created to create another file. Yes, it's like Inception. We have to go a level deeper. -- Since os.execute from lua is a pain in the ass, we escape '=' in folder names. -- It just happens that i have '=' in a folder name, so I did that one. -- I'm not particularly motivated at this point to find out what other characters might cause the same issues. exffpath=ffpath:gsub("%=","^=") os.execute("\""..exffpath.."list.bat\"")

-- Once we've created files.txt (which contains a list of all files in the fonts folder) by using list.bat, -- we read its contents into a variable called fontext. file=io.open(ffpath.."files.txt") fontext=file:read("*all") file:close()

-- We create an empty table where all filenames from fontext will go. fontslist={}

-- This line takes each line from fontext and throws it into that table above. for line in fontext:gmatch("(.-)\n") do table.insert(fontslist,line) end

-- muxbatch is the content of the muxing script, here started as an empty string. -- fc is font counter. This will be useful later so that the user knows how many fonts were found. muxbatch="" fc=0

-- Here's a loop that goes through the above-mentioned table, and for every line that ends with .ttf or .otf -- creates a line for the muxing batch to mux that font, adds it to muxbatch, and increases the font count by 1. for i=1,#fontslist do fline=fontslist[i] if fline:match("%.[TtOo][Tt][Ff]$") then fc=fc+1

-- This is the already infamous part of code where we use x-truetype-font mime type for otf as well as ttf. -- Some people are aggressively autistic about this and demand it to change. -- As we have said, currently this way works in every case we know of, -- whereas we know of countless instances where the so called 'correct' way doesn't work. -- We therefore prefer what works everywhere over what is 'correct' but doesn't work everywhere. -- Once cases appear where the incorrect way doesn't work, we will change this. -- Until then, fuck off. muxbatch=muxbatch.."--attachment-mime-type application/x-truetype-font --attach-file "..quo(fline).." " end end

-- Here's another of those endless checks. -- If muxbatch ends up being an empty string, it means no fonts were added, for whatever reason, -- and the user is therefore informed of this curious development, as it may be rather vital, -- and is given a message showing the path where the fonts were not found, -- so that he/she may know that he/she didn't put any fonts in that path, -- either because he/she didn't collect the fonts, or because he/she collected them somewhere else, -- or possibly because he/she has done some other stupid thing. if muxbatch=="" then t_error("Warning: No fonts found in "..ffpath) end

-- Here information is collected about track name and language of primary subtitles. -- Instructions for muxing script are written accordingly. if subname1~="" then tn1=" --track-name 0:"..quo(subname1) else tn1="" end if lang1~="" then ln1=" --language 0:"..lang1 else ln1="" end

-- Here the same is done for secondary subtitles if the user has decided to use said feature. (Else we go with an empty string again.) if res.sub2 then if subname2~="" then tn2=" --track-name 0:"..quo(subname2) else tn2="" end if lang2~="" then ln2=" --language 0:"..lang2 else ln2="" end subs2=tn2..ln2.." "..quo(res.subs2) else subs2="" end

-- This line determines whether we need to set the subtitle track as default. if res.defsub then defsub=" --default-track 0:true" else defsub="" end -- This is options for input video. vopt="" if res.noA then vopt=vopt.." -A" end if res.noS then vopt=vopt.." -S" end if res.noM then vopt=vopt.." -M" end if res.noC then vopt=vopt.." --no-chapters" end if res.noT then vopt=vopt.." -T --no-global-tags" end if res.VO then vopt=vopt.." "..res.vopt end -- Here the main chunk of the muxing script is written, namely the path to mkvmerge.exe, -- output video file, input video file, subtitles with information just collected a few lines above, -- chapters as collected earlier, and at the end is attached the list of fonts that we already have. muxbatch=quo(res.mmgpath)..vtitle.." -o "..quo(vpath..mvideo)..vopt.." "..quo(vpath..video)..tn1..ln1..defsub.." "..quo(res.subs)..subs2.." "..bat_chap..muxbatch

-- Here the actual muxing script is written and saved in the fonts directory. file=io.open(ffpath.."mux.bat","w") file:write(muxbatch) file:close()

-- This piece of code that will be attached at the end of the main batch file contains information to delete temporary files -- if that option has been activated, while in the opposite case, as you can surely guess by now, an empty string is supplied. if res.del then delete="\ndel \""..ffpath.."files.txt\"\ndel \""..ffpath.."mux.bat\"\ndel \"patchrel.bat\"\ndel \"xdbatch.bat\"\ndel \"sfv.lua\"\ndel \"whatisthisidonteven.sfv\"\ndel \"muxing.bat\"\n" else delete="" end

-- Here we finally come to the part where we write the main batch file that runs everything else. -- This file can be run later, assuming you don't delete any of the necessary files by then, and it will do the whole job. -- This first line adds a 'pause', that is to say prevent the cmd window from closing, when such instructions are supplied. if res.cmd then pause="pause" else pause="" end

-- This next, rather short, line utilizes a number of things that we've created earlier. -- The whole affair consist of these steps: -- 1. navigate to the fonts folder -- 2. execute mux.bat, which is the muxing script, which will mux all the necessary files -- 3. navigate to video folder -- The following apply only when such options were selected: -- 4. use Enfis to create a sfv file with CRC for the muxed video -- 5. execute sfv.lua which creates patchrel.bat and xdbatch.bat -- 6. execute patchrel, renaming the muxed file to have the CRC in name -- 7. execute xdbatch, creating the xdelta file -- 8. pause, i.e. keep cmd window open until the 'any' key is P -- 9. delete temporary files BAT="cd /d "..quo(ffpath).."\ncall mux.bat\ncd /d "..quo(vpath).."\n"..bat_crc..pause..delete

-- batch is the location of muxing.bat batch=vpath.."muxing.bat"

-- Here all the instructions are being written into muxing.bat. local xfile=io.open(batch,"w") xfile:write(BAT) xfile:close()

-- As a last step, all relevant information about the operation to take place is collected here, -- and it will be displayed for the user to glance over and confirm that everything looks as it's supposed to, -- or realize that in his/her boundless stupidity he/she did something wrong and has to start again. summary="Files to mux:\n\nVideo file: "..video.."\nSubtitle file 1: "..res.subs.."\nSubtitle file 2: "..res.subs2.."\nFonts to mux: "..fc.."\n\nMuxed file: "..mvideo.."\n\nBatch file: "..batch.."\n\nYou can mux now or run this batch file later.\nIf muxing from Aegisub doesn't work,\njust run the batch file.\n\nMux now?"

-- Here we display the dialog where the user can choose to either commence operations at once, -- or leave that for a later time. P=ADD({{class="label",label=summary}},{"Yes","No"},{ok='Yes',close='No'}) if P=="Yes" then -- If a decision is made to proceed, the user is informed that muxing is taking place -- while a cmd window should be executing all scheduled operations. aegisub.progress.title("Muxing...")

-- As mentioned before, ox.execute from lua is a pest, and thus an escape sequence is implemented again. batch=batch:gsub("%=","^=")

-- This is where things finally start to move and you await anxiously the verdict of either success or failure. os.execute(quo(batch)) end end

-- This little function is used for the Open dialog. -- It takes the result, applies it to the corresponding field in the GUI, -- and updates all other fields with current values. function gui(a,b) for k,v in ipairs(GUI) do if b==nil then b="" end if v.name==a then v.value=b else v.value=res[v.name] end end end

-- This function sends an error message to the user, and if given such instructions, cancels all operations. function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end

-- This little function takes a string and wraps it in quotation marks. function quo(x) x="\""..x.."\"" return x end

-- This converts boolean values to their corresponding text counterparts. function tf(val) if val==true then ret="true" elseif val==false then ret="false" else ret=val end return ret end

-- This, contrary to the previous function, takes strings, converts 'true' or 'false' to boolean values, -- and leaves anything else as is. function detf(txt) if txt=="true" then ret=true elseif txt=="false" then ret=false else ret=txt end return ret end

-- This escape function is used for gsub. function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function logg(m) m=m or "nil" aegisub.log("\n "..m) end

-- This line, as I'm sure everyone knows, registers the mux function in Aegisub so that it appears in the menu and you can use it. -- If you have DependencyCotrol, it registers it with DependencyCotrol. if haveDepCtrl then depRec:registerMacro(mux) else aegisub.register_macro(script_name,script_description,mux) end script_name="Runemap" script_description="おまえ は ばか だから" script_author="unanimated" script_version="1.0" re=require'aegisub.re' function map(subs,sel) GUI={ {x=0,y=11,width=17,class="label",label="Romaji"}, {x=0,y=12,width=17,class="edit",name="rmj"}, {x=18,y=11,width=17,class="label",label="Hiragana"}, {x=18,y=12,width=17,class="edit",name="hrg"}, {x=36,y=11,width=17,class="label",label="Katakana"}, {x=36,y=12,width=17,class="edit",name="ktk"}, {x=0,y=8,width=53,class="edit",name="hrgc", value="あいうえお やゆよ かきくけこ さしすせそ たちつてと なにぬねの はひふへほ まみむめも らりるれろ がぎぐげご ざじずぜぞ だぢづでど ばびぶべぼ ぱぴぷぺぽ わをん っゃゅょー"}, {x=0,y=9,width=53,class="edit",name="ktkc", value="アイウエオ ヤユヨ カキクケコ サシスセソ タチツテトチ ナ二ヌネノ ハヒフヘホ マミムメモ ラリルレロ ガギグゲゴ ザジズゼゾ ダヂヅデド バビブベボ パピプペポ ワヲン ッャュョー"}, } a1={"a","i","u","e","o","ya","yu","yo"} a2={"あ","い","う","え","お","や","ゆ","よ"} a3={"ア","イ","ウ","エ","オ","ヤ","ユ","ヨ"} ka1={"ka","ki","ku","ke","ko","kya","kyu","kyo"} ka2={"か","き","く","け","こ","きゃ","きゅ","きょ"} ka3={"カ","キ","ク","ケ","コ","キャ","キュ","キョ"} sa1={"sa","shi","su","se","so","sha","shu","sho"} sa2={"さ","し","す","せ","そ","しゃ","しゅ","しょ"} sa3={"サ","シ","ス","セ","ソ","シャ","シュ","ショ"} ta1={"ta","chi","tsu","te","to","cha","chu","cho"} ta2={"た","ち","つ","て","と","ちゃ","ちゅ","ちょ"} ta3={"タ","チ","ツ","テ","ト","チャ","チュ","チョ"} na1={"na","ni","nu","ne","no","nya","nyu","nyo"} na2={"な","に","ぬ","ね","の","にゃ","にゅ","にょ"} na3={"ナ","二","ヌ","ネ","ノ","ニャ","ニュ","ニョ"} ha1={"ha","hi","fu","he","ho","hya","hyu","hyo"} ha2={"は","ひ","ふ","へ","ほ","ひゃ","ひゅ","ひお"} ha3={"ハ","ヒ","フ","ヘ","ホ","ヒャ","ヒュ","ヒョ"} ma1={"ma","mi","mu","me","mo","mya","myu","myo"} ma2={"ま","み","む","め","も","みゃ","みゅ","みょ"} ma3={"マ","ミ","ム","メ","モ","ミャ","ミュ","ミョ"} ra1={"ra","ri","ru","re","ro","rya","ryu","ryo"} ra2={"ら","り","る","れ","ろ","りゃ","りゅ","りょ"} ra3={"ラ","リ","ル","レ","ロ","リャ","リュ","リョ"} ga1={"ga","gi","gu","ge","go","gya","gyu","gyo"} ga2={"が","ぎ","ぐ","げ","ご","ぎゃ","ぎゅ","ぎょ"} ga3={"ガ","ギ","グ","ゲ","ゴ","ギャ","ギュ","ギョ"} za1={"za","ji","zu","ze","zo","ja","ju","jo"} za2={"ざ","じ","ず","ぜ","ぞ","じゃ","じゅ","じょ"} za3={"ザ","ジ","ズ","ゼ","ゾ","ジャ","ジュ","ジョ"} da1={"da","ji","zu","de","do","ja","ju","jo"} da2={"だ","ぢ","づ","で","ど","ぢゃ","ぢゅ","ぢょ"} da3={"ダ","ヂ","ヅ","デ","ド","ヂャ","ヂュ","ヂョ"} ba1={"ba","bi","bu","be","bo","bya","byu","byo"} ba2={"ば","び","ぶ","べ","ぼ","びゃ","びゅ","びょ"} ba3={"バ","ビ","ブ","ベ","ボ","ビャ","ビュ","ビョ"} pa1={"pa","pi","pu","pe","po","pya","pyu","pyo"} pa2={"ぱ","ぴ","ぷ","ぺ","ぽ","ぴゃ","ぴゅ","ぴょ"} pa3={"パ","ピ","プ","ペ","ポ","ピャ","ピュ","ピョ"} wa1={"wa","wo","n","_","-"} wa2={"わ","を","ん","っ","ー"} wa3={"ワ","ヲ","ン","ッ","ー"} for i=1,8 do n=i-1 t1={x=n,y=0,class="label",name=a1[i].."1",label=a1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=a1[i].."2",label=a2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=a1[i].."3",label=a3[i]} table.insert(GUI,t3) t4={x=n,y=4,class="label",name=ra1[i].."1",label=ra1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=ra1[i].."2",label=ra2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=ra1[i].."3",label=ra3[i]} table.insert(GUI,t6) n=i+8 t1={x=n,y=0,class="label",name=ka1[i].."1",label=ka1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=ka1[i].."2",label=ka2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=ka1[i].."3",label=ka3[i]} table.insert(GUI,t3) t4={x=n,y=4,class="label",name=ga1[i].."1",label=ga1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=ga1[i].."2",label=ga2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=ga1[i].."3",label=ga3[i]} table.insert(GUI,t6) n=i+17 t1={x=n,y=0,class="label",name=sa1[i].."1",label=sa1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=sa1[i].."2",label=sa2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=sa1[i].."3",label=sa3[i]} table.insert(GUI,t3) t4={x=n,y=4,class="label",name=za1[i].."1",label=za1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=za1[i].."2",label=za2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=za1[i].."3",label=za3[i]} table.insert(GUI,t6) n=i+26 t1={x=n,y=0,class="label",name=ta1[i].."1",label=ta1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=ta1[i].."2",label=ta2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=ta1[i].."3",label=ta3[i]} table.insert(GUI,t3) t4={x=n,y=4,class="label",name=da1[i].."1",label=da1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=da1[i].."2",label=da2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=da1[i].."3",label=da3[i]} table.insert(GUI,t6) n=i+35 t1={x=n,y=0,class="label",name=na1[i].."1",label=na1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=na1[i].."2",label=na2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=na1[i].."3",label=na3[i]} table.insert(GUI,t3) t4={x=n,y=4,class="label",name=ba1[i].."1",label=ba1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=ba1[i].."2",label=ba2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=ba1[i].."3",label=ba3[i]} table.insert(GUI,t6) n=i+44 t1={x=n,y=0,class="label",name=ha1[i].."1",label=ha1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=ha1[i].."2",label=ha2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=ha1[i].."3",label=ha3[i]} table.insert(GUI,t3) t4={x=n,y=4,class="label",name=pa1[i].."1",label=pa1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=pa1[i].."2",label=pa2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=pa1[i].."3",label=pa3[i]} table.insert(GUI,t6) n=i+53 t1={x=n,y=0,class="label",name=ma1[i].."1",label=ma1[i]} table.insert(GUI,t1) t2={x=n,y=1,class="label",name=ma1[i].."2",label=ma2[i]} table.insert(GUI,t2) t3={x=n,y=2,class="label",name=ma1[i].."3",label=ma3[i]} table.insert(GUI,t3) if i<6 then t4={x=n,y=4,class="label",name=wa1[i].."1",label=wa1[i]} table.insert(GUI,t4) t5={x=n,y=5,class="label",name=wa1[i].."2",label=wa2[i]} table.insert(GUI,t5) t6={x=n,y=6,class="label",name=wa1[i].."3",label=wa3[i]} table.insert(GUI,t6) end end

repeat if P=="Transcribe" then R=res.rmj H=res.hrg K=res.ktk if R=="" then RC=0 else RC=1 end if H=="" then HC=0 else HC=1 end if K=="" then KC=0 else KC=1 end if RC+HC+KC>1 then t_error("Error: Multiple inputs.") rom=R hira=H kata=K elseif RC+HC+KC==1 then rom="" hira="" kata="" -- hiragana if HC==1 then hira=H tab={} chars=re.find(H,".ゃ?") for l=1,#chars do table.insert(tab,chars[l].str) end for i=1,#tab do local c=tab[i] name=getname(c) romc=getchar(name,"1") katac=getchar(name,"3") rom=rom..romc kata=kata..katac end -- katakana elseif KC==1 then kata=K tab={} chars=re.find(K,".ャ?") for l=1,#chars do table.insert(tab,chars[l].str) end for i=1,#tab do local c=tab[i] name=getname(c) romc=getchar(name,"1") hirac=getchar(name,"2") rom=rom..romc hira=hira..hirac end -- romaji else rom=R:gsub("%-"," ") tab={} for chars in rom:gmatch(".-[aeiou]") do if chars:match("n.y?[aeiou]") then table.insert(tab,"n") table.insert(tab,chars:match("n(.y?[aeiou])")) elseif chars:match("n%s.-[aeiou]") then table.insert(tab,"n") table.insert(tab," ") table.insert(tab,chars:match("^n%s(.-[aeiou])")) elseif chars:match("^%s.-[aeiou]") then table.insert(tab," ") table.insert(tab,chars:match("^%s(.-[aeiou])")) elseif chars:match("^.")==chars:match("^.(.)") then table.insert(tab,"_") table.insert(tab,chars:match("^.(.y?[aeiou])")) else table.insert(tab,chars) end end if rom:match("n$") then table.insert(tab,"n") end for i=1,#tab do local c=tab[i] name=getname(c) hirac=getchar(name,"2") katac=getchar(name,"3") hira=hira..hirac kata=kata..katac end end rom=rom:gsub("_(.)","%1%1") :gsub("(.)%-","%1%1") end for k,v in ipairs(GUI) do if v.name=="rmj" then v.value=rom end if v.name=="hrg" then v.value=hira end if v.name=="ktk" then v.value=kata end end end buttons={"Transcribe","Exit"} P,res=aegisub.dialog.display(GUI,buttons,{ok='Transcribe',close='Exit'}) until P=="Exit" if P=="Exit" then aegisub.cancel() end return sel end function getname(c) local n for k,v in ipairs(GUI) do if v.label==c then n=v.name end end n=n or c n=n:gsub("%d","") return n end function getchar(n,t) local c for k,v in ipairs(GUI) do if v.name==n..t and c==nil then c=v.label end end c=c or n return c end function t_error(message,cancel) aegisub.dialog.display({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then aegisub.cancel() end end aegisub.register_macro(script_name,script_description,map) -- Know that feeling when you're editng and re-editing a line until you can't remember what the original was and you have to look it up? -- If not, then go away. If you do, then here's a solution to let you easily see the original and edited line side by side. -- The idea is that you save a backup, and then with a hotkey quickly bring up the saved lines for whatever you have selected. -- "Save Backup" saves the current script to memory. (This gets erased if you reload automation scripts.) -- "Save to File" saves it to a file with the name you see there, in the .ass script's folder. (You can make any number of those.) -- "Load from File" loads lines from the file with the filename you see. (Type to change if you want from a different one.) -- "Load from Memory" loads from memory, which is also what gets loaded by default (if you saved it before). -- "Memory to File" saves the content of memory to a file. -- "File to Memory" loads the content of a file to memory. -- You can easily switch between different backups. -- If you split/join lines, the backup will be off by those, but you can just select more lines to load the ones you need to see. script_name="Backup Checker" script_description="Backup Checker" script_author="unanimated" script_version="1.1" function save(subs, sel) B={} for i=1, #subs do if subs[i].class=="dialogue" then text=subs[i].text table.insert(B,text) end end end function savef(subs, sel) BF="" for i=1, #subs do if subs[i].class=="dialogue" then text=subs[i].text BF=BF..text.."\n" end end BF=BF:gsub("\n$","") local file=io.open(scriptpath.."\\"..filename, "w") file:write(BF) file:close() end function back(subs,sel,act) scriptpath=aegisub.decode_path("?script") scriptname=aegisub.file_name() savename=scriptname:gsub("%.ass","_backup.bak") if #sel<=4 then boxheight=6 end if #sel>=5 and #sel<9 then boxheight=8 end if #sel>=9 and #sel<15 then boxheight=math.ceil(#sel*0.8) end if #sel>=15 and #sel<18 then boxheight=12 end if #sel>=18 then boxheight=15 end c=0 for i=1, #subs do if subs[i].class=="dialogue" then break end c=c+1 end if B==nil then B={} end data="" for x, i in ipairs(sel) do if B[i-c]==nil then line="" else line=B[i-c] end data=data..line.."\n" end data=data:gsub("\n$","") if not data:match"%a" then data="--- Nothing saved yet. Click 'Save Backup' / 'Save to File' to back up the script now, or load backup from a file. ---" end rine=act-c gui={ {x=0,y=0,width=33,height=1,class="label",name="top",label="Saved Text (Memory)"}, {x=0,y=1,width=45,height=boxheight,class="textbox",name="dat",value=data}, {x=33,y=0,width=5,height=1,class="label",label="Active Line: "..rine.."/"..#subs-c.." " }, {x=38,y=0,width=7,height=1,class="label",label="Backup Checker v"..script_version},

{x=0,y=boxheight+1,width=1,height=1,class="label",label="Filename:"}, {x=1,y=boxheight+1,width=15,height=1,class="edit",name="file",value=savename}, {x=16,y=boxheight+1,width=17,height=1,class="edit",name="msg"}, {x=33,y=boxheight+1,width=12,height=1,class="edit",name="idk"}, } but={"Load from Memory","Load from File","Save to Memory","Save to File","Memory to File","File to Memory","No Comments","OK"} repeat if pressed=="Load from File" then FB={} load=io.open(scriptpath.."\\"..filename) if load~=nil then fileback=load:read("*all") io.close(load) fileback=fileback.."\n" ldata="" for l in fileback:gmatch("(.-)\n") do table.insert(FB,l) end for x, i in ipairs(sel) do if FB[i-c]==nil then FB[i-c]="" end ldata=ldata..FB[i-c].."\n" end for key,val in ipairs(gui) do if val.name=="dat" then val.value=ldata elseif val.name=="top" then val.label="Saved Text (File)" elseif val.name=="msg" then val.value="" else val.value=res[val.name] end end else for key,val in ipairs(gui) do if val.name=="msg" then val.value="» File \""..filename.."\" not found. «" elseif val.name=="file" then val.value=savename else val.value=res[val.name] end end end end if pressed=="Load from Memory" then for key,val in ipairs(gui) do if val.name=="dat" then val.value=data elseif val.name=="top" then val.label="Saved Text (Memory)" elseif val.name=="msg" then val.value="" else val.value=res[val.name] end end end if pressed=="Memory to File" then if #B>0 then BF="" for i=1,#B do BF=BF..B[i].."\n" end BF=BF:gsub("\n$","") local file=io.open(scriptpath.."\\"..filename, "w") file:write(BF) file:close() end for key,val in ipairs(gui) do if val.name=="file" then val.value=savename elseif val.name=="msg" then if #B==0 then val.value="Nothing in memory." else val.value="Backup saved to "..filename end else val.value=res[val.name] end end end if pressed=="File to Memory" then load=io.open(scriptpath.."\\"..filename) if load~=nil then fileback=load:read("*all") io.close(load) fileback=fileback.."\n" B={} for l in fileback:gmatch("(.-)\n") do table.insert(B,l) end data="" for x, i in ipairs(sel) do if B[i-c]==nil then line="" else line=B[i-c] end data=data..line.."\n" end for key,val in ipairs(gui) do if val.name=="file" then val.value=savename elseif val.name=="msg" then val.value="Saved from "..filename.." to memory." else val.value=res[val.name] end end else for key,val in ipairs(gui) do if val.name=="msg" then val.value="File "..filename.." not found." else val.value=res[val.name] end end end end if pressed=="No Comments" then for key,val in ipairs(gui) do if val.name=="dat" then val.value=res.dat:gsub("{[^\\}]-}","") else val.value=res[val.name] end end end pressed,res=aegisub.dialog.display(gui,but,{close='OK'}) filename=res.file if not filename:match("%.bak$") then filename=filename..".bak" end presd="Load from File,Load from Memory,Memory to File,File to Memory,No Comments" until not presd:match(pressed)

if pressed=="OK" then aegisub.cancel() end if pressed=="Save to Memory" then save(subs,sel) end if pressed=="Save to File" then savef(subs,sel) end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, back) script_name="Modify Text" script_description="A collection of scripts for text modification" script_author="unanimated" script_version="1.0" function italicize(subs,sel) for z, i in ipairs(sel) do local l=subs[i] text=l.text styleref=stylechk(subs,l.style) local si=styleref.italic if si==false then it="1" else it="0" end text=text:gsub("\\i([\\}])","\\i".. 1-it.."%1") if text:match("^{[^}]*\\i%d[^}]*}") then text=text:gsub("\\i(%d)", function(num) return "\\i".. 1-num end) else if text:match("\\i([01])") then italix=text:match("\\i([01])") end if italix==it then text=text:gsub("\\i(%d)", function(num) return "\\i".. 1-num end) end text="{\\i"..it.."}"..text text=text:gsub("{\\i(%d)}({\\[^}]*)}","%2\\i%1}") end l.text=text subs[i]=l end end function bold(subs,sel) for z, i in ipairs(sel) do local l=subs[i] text=l.text styleref=stylechk(subs,l.style) local sb=styleref.bold if sb==false then b="1" else b="0" end text=text:gsub("\\b([\\}])","\\b".. 1-b.."%1") if text:match("^{[^}]*\\b%d[^}]*}") then text=text:gsub("\\b(%d)", function(num) return "\\b".. 1-num end) else if text:match("\\b([01])") then bolt=text:match("\\b([01])") end if bolt==b then text=text:gsub("\\b(%d)", function(num) return "\\b".. 1-num end) end text="{\\b"..b.."}"..text text=text:gsub("{\\b(%d)}({\\[^}]*)}","%2\\b%1}") end l.text=text subs[i]=l end end function lowercase(subs,sel) for x, i in ipairs(sel) do local line=subs[i] local text=subs[i].text text=text:gsub("\\n","small_break") text=text:gsub("\\N","large_break") text=text:gsub("\\h","hard_space") text=text:gsub("^([^{]*)", function (l) return l:lower() end) text=text:gsub("}([^{]*)", function (l) return "}"..l:lower() end) text=text:gsub("small_break","\\n") text=text:gsub("large_break","\\N") text=text:gsub("hard_space","\\h") line.text=text subs[i]=line end end function uppercase(subs,sel) for x, i in ipairs(sel) do local line=subs[i] local text=subs[i].text text=text:gsub("\\n","SMALL_BREAK") text=text:gsub("\\N","LARGE_BREAK") text=text:gsub("\\h","HARD_SPACE") text=text:gsub("^([^{]*)", function (u) return u:upper() end) text=text:gsub("}([^{]*)", function (u) return "}"..u:upper() end) text=text:gsub("SMALL_BREAK","\\n") text=text:gsub("LARGE_BREAK","\\N") text=text:gsub("HARD_SPACE","\\h") line.text=text subs[i]=line end end function capitalines(subs,sel) for x, i in ipairs(sel) do local line=subs[i] local text=subs[i].text text=text:gsub("^(%l)([^{]-)", function (c,d) return c:upper()..d end) text=text:gsub("^({[^}]-})(%l)([^{]-)", function (e,f,g) return e..f:upper()..g end) text=text:gsub(" i "," I ") text=text:gsub(" i'"," I'") text=text:gsub("\\Ni ","\\NI ") text=text:gsub("\\Ni'","\\NI'") line.text=text subs[i]=line end end function sentences(subs,sel) for x, i in ipairs(sel) do local line=subs[i] local text=subs[i].text text=text:gsub("^(%l)([^{]-)", function (c,d) return c:upper()..d end) text=text:gsub("^({[^}]-})(%l)([^{]-)", function (e,f,g) return e..f:upper()..g end) text=text:gsub("([%.?!]%s)(%l)", function (k,l) return k..l:upper() end) text=text:gsub(" i "," I ") text=text:gsub(" i'"," I'") text=text:gsub("\\Ni ","\\NI ") text=text:gsub("\\Ni'","\\NI'") line.text=text subs[i]=line end end word={"The","A","An","At","As","On","Of","Or","For","Nor","With","Without","Within","To","Into","Onto","Unto"," And","But","In","Inside","By","Till","From","Over","Above","About","Around","After","Against","Along","Below"," Beneath","Beside","Between","Beyond","Under","Until","Via"} vord={"the","a","an","at","as","on","of","or","for","nor","with","without","within","to","into","onto","unto","and","but" ,"in","inside","by","till","from","over","above","about","around","after","against","along","below","beneath","beside"," between","beyond","under","until","via"} function capitalize(subs,sel) for x, i in ipairs(sel) do local line=subs[i] local text=subs[i].text text=text:gsub("\\n","*small_break*") text=text:gsub("\\N","*large_break*") text=text:gsub("\\h","*hard_space*") text=text:gsub("^(%l)(%l-)", function (c,d) return c:upper()..d end) -- start of line text=text:gsub("([%s\"}%(%-=])(%l)(%l-)", function (e,f,g) return e..f:upper()..g end) -- after: space " } ( - = text=text:gsub("(break%*)(%l)(%l-)", function (h,j,k) return h..j:upper()..k end) -- after \N text=text:gsub("%s([\'])(%l)(%l-)", function (l,m,n) return " "..l..m:upper()..n end) -- after space+' text=text:gsub("^(\')(%l)(%l-)", function (l,m,n) return l..m:upper()..n end) -- start of line+'

for r=1,#word do w=word[r] v=vord[r] text=text:gsub("([^%.%:])%s"..w.."%s","%1 "..v.." ") text=text:gsub("([^%.%:])%s({[^}]-})"..w.."%s","%1 %2"..v.." ") end

-- other stuff text=text:gsub("$","#") text=text:gsub("(%s?)([IVXLCDM])([ivxlcdm]+)([%s%p#])",function (s,r,m,e) return s..r..m:upper()..e end) - - Roman numbers text=text:gsub("LID","Lid") text=text:gsub("DIM","Dim") text=text:gsub("Ok([%s%p#])","OK%1") text=text:gsub("%-San([%s%p#])","-san%1") text=text:gsub("%-Kun([%s%p#])","-kun%1") text=text:gsub("%-Chan([%s%p#])","-chan%1") text=text:gsub("%-Sama([%s%p#])","-sama%1") text=text:gsub("%-Dono([%s%p#])","-dono%1") text=text:gsub("#$","") text=text:gsub("%*small_break%*","\\n") text=text:gsub("%*large_break%*","\\N") text=text:gsub("%*hard_space%*","\\h")

line.text=text subs[i]=line end end function strikealpha(subs,sel) for x, i in ipairs(sel) do local l=subs[i] l.text=l.text:gsub("\\s1","\\alpha&H00&") l.text=l.text:gsub("\\s0","\\alpha&HFF&") l.text=l.text:gsub("\\u1","\\alpha&HFF&") l.text=l.text:gsub("\\u0","\\alpha&H00&") subs[i]=l end end function an8q2(subs,sel) for x, i in ipairs(sel) do local line=subs[i] local text=subs[i].text if res.anq=="\\an8" then if line.text:match("\\an%d") then text=text:gsub("\\an%d","") text=text:gsub("{}","") else text="{\\an8}"..text text=text:gsub("{\\an8}{\\","{\\an8\\") end end if res.anq=="\\q2" then if text:match("\\q2") then text=text:gsub("\\q2","") text=text:gsub("{}","") else text="{\\q2}"..text text=text:gsub("{\\q2}{\\","{\\q2\\") end end line.text=text subs[i]=line end end function honorifix(subs, sel) for i=#subs,1,-1 do if subs[i].class=="dialogue" then local line=subs[i] local text=subs[i].text text=text :gsub("%-san([^%a]?)","{-san}%1") :gsub("%-chan([^%a]?)","{-chan}%1") :gsub("%-kun([^%a]?)","{-kun}%1") :gsub("%-sama([^%a]?)","{-sama}%1") :gsub("%-niisan","{-niisan}") :gsub("%-oniisan","{-oniisan}") :gsub("%-oniichan","{-oniichan}") :gsub("%-oneesan","{-oneesan}") :gsub("%-oneechan","{-oneechan}") :gsub("%-oneesama","{-oneesama}") :gsub("%-neesama","{-neesama}") :gsub("%-sensei","{-sensei}") :gsub("%-se[mn]pai","{-senpai}") :gsub("%-dono","{-dono}") :gsub("Onii{%-chan}","Brother{Onii-chan}") :gsub("Onii{%-san}","Brother{Onii-san}") :gsub("Onee{%-chan}","Sister{Onee-chan}") :gsub("Onee{%-san}","Sister{Onee-san}") :gsub("Onee{%-sama}","Sister{Onee-sama}") :gsub("onii{%-chan}","brother{onii-chan}") :gsub("onii{%-san}","brother{onii-san}") :gsub("onii{%-sama}","brother{onii-sama}") :gsub("onee{%-chan}","sister{onee-chan}") :gsub("onee{%-san}","sister{onee-san}") :gsub("onee{%-sama}","sister{onee-sama}") :gsub("{{","{") :gsub("}}","}") :gsub("({[^{}]-){(%-%a-)}([^{}]-})","%1%2%3") line.text=text subs[i]=line end end end function stylechk(subs,stylename) for i=1,#subs do if subs[i].class=="style" then style=subs[i] if stylename==style.name then styleref=style break end end end return styleref end function textmod(subs,sel) dialog_config={ {x=0,y=0,width=1,height=1,class="dropdown",name="ita",items={"italicize","bold"},value="italicize" }, {x=1,y=0,width=1,height=1,class="dropdown",name="capi",items={"capit. words","capit. lines","capit. sentences","lowercase","uppercase"},value="capit. words" }, {x=2,y=0,width=1,height=1,class="dropdown",name="anq",items={"\\an8","\\q2"},value="\\an8" }, } pressed, res=aegisub.dialog.display(dialog_config,{"itabold","capitalize","an8 / q2","honorifix","cancel"},{close='cancel'}) if pressed=="cancel" then aegisub.cancel() end

if pressed=="itabold" then if res.ita=="italicize" then italicize(subs,sel) end if res.ita=="bold" then bold(subs,sel) end end if pressed=="capitalize" then if res.capi=="capit. words" then capitalize(subs,sel) end if res.capi=="capit. lines" then capitalines(subs,sel) end if res.capi=="capit. sentences" then sentences(subs,sel) end if res.capi=="lowercase" then lowercase(subs,sel) end if res.capi=="uppercase" then uppercase(subs,sel) end end if pressed=="an8 / q2" then an8q2(subs,sel) end if pressed=="honorifix" then honorifix(subs,sel) end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, textmod) -- This will generate chapters from the .ass file -- MARKER: For a line to be used for chapters, it has to be marked with "chapter"/"chptr"/"chap" in actor/effect field (depending on settings) -- or the same 3 options as a separate comment, ie. {chapter} etc. -- CHAPTER NAME: What will be used as chapter name. It's either the content of the effect field, or the FIRST comment. -- If the comment is {OP first frame} or {ED start}, the script will remove " first frame" or " start", so you can keep those. -- If you use default settings, just put "chapter" in actor field and make comments like {OP} or {Part A}. script_name = "Make Chapters" script_description = "Makes chapters from marked lines" script_author = "unanimated" script_version = "1.03"

-- SETTINGS -- default_marker="actor" -- options: "actor","effect","comment" default_chapter_name="comment" -- options: "comment","effect" default_save_name="script" -- options: "script","video" autogenerate_intro=true -- options: true / false

------require "clipboard" function chopters(subs, sel) euid=0 chptrs={} for i = 1, #subs do if subs[i].class == "info" then if subs[i].key=="Video File" then videoname=subs[i].value videoname=videoname:gsub("%.mkv","") end end

if subs[i].class == "dialogue" then local line = subs[i] local text=subs[i].text local actor=line.actor local effect=line.effect local start=line.start_time if text:match("{[Cc]hapter}") or text:match("{[Cc]hptr}") or text:match("{[Cc]hap}") then comment="chapter" else comment="" end if res.marker=="actor" then marker=actor:lower() end if res.marker=="effect" then marker=effect:lower() end if res.marker=="comment" then marker=comment:lower() end

if marker=="chapter" or marker=="chptr" or marker=="chap" then if res.nam=="comment" then name=text:match("^{([^}]*)}") name=name:gsub(" [Ff]irst [Ff]rame","") name=name:gsub(" [Ss]tart","") name=name:gsub("part a","Part A") name=name:gsub("part b","Part B") name=name:gsub("preview","Preview") else name=effect end

lineid=start+20

timecode=math.floor(start/1000) tc1=math.floor(timecode/60) tc2=timecode%60 tc3=start%1000 tc4="00" if tc2==60 then tc2=0 tc1=tc1+1 end if tc1>119 then tc1=tc1-120 tc4="02" end if tc1>59 then tc1=tc1-60 tc4="01" end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end if tc3<100 then tc3="0"..tc3 end linetime=tc4..":"..tc1..":"..tc2.."."..tc3

cur_chptr={id=lineid,name=name,tim=linetime} table.insert(chptrs,cur_chptr)

end if line.style=="Default" then euid=euid+text:len() end end end

insert_chapters=""

if res.intro then insert_chapters=" \n "..#subs.."\n 0\n 1\n \n Intro\n eng\n \n 00:00:00.033\n \n"

end

table.sort(chptrs,function(a,b) return a.tim

for c=1,#chptrs do local ch=chptrs[c]

ch_uid=ch.id ch_name=ch.name ch_time=ch.tim

chapter=" \n "..ch_uid.."\n 0\n 1\n \n "..ch_name.."\n eng\n \n "..ch_time.."\n \n"

insert_chapters=insert_chapters..chapter end

chapters="\n\n\n \n 0\n 0\n "..euid.."\n"..insert_chapters.." \n"

chdialog= {{x=0,y=0,width=35,height=1,class="label",label="Text to export:"}, {x=0,y=1,width=35,height=20,class="textbox",name="copytext",value=chapters}, {x=0,y=21,width=35,height=1,class="label",label="File will be saved in the same folder as the .ass file."},}

pressed,reslt=aegisub.dialog.display(chdialog,{"Save xml file","Cancel","Copy to clipboard",},{cancel='Cancel'}) if pressed=="Copy to clipboard" then clipboard.set(chapters) end if pressed=="Save xml file" then scriptpath=aegisub.decode_path("?script") scriptname=aegisub.file_name() scriptname=scriptname:gsub("%.ass","") if res.sav=="script" then filename=scriptname else filename=videoname end local file = io.open(scriptpath.."\\"..filename..".xml", "w") file:write(chapters) file:close() end end function gui(subs, sel) dialog_config= { {x=0,y=0,width=1,height=1,class="label",label="Use as chapter marker:"},

{x=1,y=0,width=1,height=1,class="dropdown",name="marker",items={"actor","effect","comment"},value=default_mar ker}, {x=0,y=1,width=1,height=1,class="label",label="Get chapter name from:"},

{x=1,y=1,width=1,height=1,class="dropdown",name="nam",items={"comment","effect"},value=default_chapter_name }, {x=0,y=2,width=1,height=1,class="label",label="Get filename for saving from: "}, {x=1,y=2,width=1,height=1,class="dropdown",name="sav",items={"script","video"},value=default_save_name},

{x=0,y=3,width=2,height=1,class="checkbox",name="intro",label="autogenerate \"Intro\" chapter",value=autogenerate_intro,}, } pressed, res = aegisub.dialog.display(dialog_config, {"Generate","Help","Cancel"},{ok='Generate',cancel='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Generate" then chopters(subs, sel) end if pressed=="Help" then aegisub.dialog.display({ {x=0,y=0,width=1,height=1,class="label",label="Chapter marker: "}, {x=0,y=3,width=1,height=1,class="label",label="Chapter name:"}, {x=0,y=6,width=1,height=1,class="label",label="Save name:"}, {x=1,y=0,width=1,height=3,class="label",label="actor - actor field must be exact match for one of these: \"chapter\", \"chptr\", \"chap\"\neffect - effect field must be exact match for one of these: \"chapter\", \"chptr\", \"chap\" \ncomment - text must contain one of these comments: \"{chapter}\", \"{chptr}\", \"{chap}\"\n(no quotation marks)"}, {x=1,y=3,width=1,height=3,class="label",label="comment - the FIRST comment in text field will be used. {OP}, {Part A}, etc.\nThe script will remove \" first frame\" or \" start\", so you can use {OP first frame} or {ED start}.\neffect - the content of the effect field will be used\n"}, {x=1,y=6,width=1,height=3,class="label",label="The name of either the .ass file or the video file will be used with .xml extension. \nFile gets saved in the same folder as the .ass.\n"}, },{"Wakatta"},{cancel='Wakatta'}) end end function makechapters(subs, sel) gui(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, makechapters) -- Moves the last word of the line to the start of the next, or the first word of the next line to the end of the active one. script_name="Re-Split" script_description="Resplits lines at a different place" script_author="unanimated" script_version="1.1" function resplitl(subs,sel,act) line=subs[act] text=line.text if act<#subs then nl=subs[act+1] first=nl.text:match("^([%w']+%p?) ") if first==nil then first=nl.text:match("^{\\[^}]-}([%w']+%p?) ") end if first~=nil then nl.text=nl.text:gsub("^([%w']+%p?) ","") :gsub("^({\\[^}]-})[%w']+%p? ","%1") text=text.." "..first end subs[act+1]=nl end line.text=text subs[act]=line aegisub.set_undo_point(script_name) return sel end function resplitr(subs,sel,act) line=subs[act] text=line.text if act<#subs then nl=subs[act+1] last=text:match("[} ]([%w']+%p?)$") if last~=nil then text=text:gsub("%s?[%w']+%p?$","") :gsub("%s*{\\[^}]-}$","") if nl.text:match("^{\\[^}]-}") then nl.text=nl.text:gsub("^({\\[^}]-})","%1"..last.." ") else nl.text=last.." "..nl.text end end subs[act+1]=nl end line.text=text subs[act]=line aegisub.set_undo_point(script_name) return sel end aegisub.register_macro("ReSplit - Backward",script_description,resplitl) aegisub.register_macro("ReSplit - Forward",script_description,resplitr) script_name = "Convert Framerate" script_description = "does what you'd expect it to do" script_author = "unanimated" script_version = "1.0" function framerate(subs) f1=res.fps1 f2=res.fps2 for i = 1, #subs do if subs[i].class == "dialogue" then local line = subs[i] line.start_time=line.start_time/f2*f1 line.end_time=line.end_time/f2*f1 subs[i] = line end end end function shiift(subs, sel) shift=res.ms for i = 1, #subs do if subs[i].class == "dialogue" then local line = subs[i] start=line.start_time endt=line.end_time if res.shift=="shift back" then start=start-shift endt=endt-shift else start=start+shift endt=endt+shift end line.start_time=start line.end_time=endt subs[i] = line end end end function convertfps(subs, sel) dialog_config= { {x=0,y=0,width=2,height=1,class="label",label="Convert framerate",}, {x=0,y=1,width=1,height=1,class="label",label="from:",}, {x=0,y=2,width=1,height=1,class="label",label="to:",}, {x=1,y=1,width=1,height=1,class="dropdown",name="fps1",items={23.976,24,25,29.970,30},value=23.976}, {x=1,y=2,width=1,height=1,class="dropdown",name="fps2",items={23.976,24,25,29.970,30},value=25},

{x=3,y=0,width=2,height=1,class="checkbox",name="custom",label="custom framerates",value=false,}, {x=3,y=1,width=1,height=1,class="floatedit",name="fps1c",value=0,}, {x=3,y=2,width=1,height=1,class="floatedit",name="fps2c",value=0,},

{x=0,y=4,width=2,height=1,class="label",label="Shift subtitles",}, {x=3,y=4,width=2,height=1,class="label",label="in milliseconds:",}, {x=0,y=5,width=2,height=1,class="dropdown",name="shift",items={"shift back","shift forward"},value="shift back"}, {x=3,y=5,width=1,height=1,class="floatedit",name="ms",value=0,},

{x=0,y=6,width=2,height=1,class="label",}, } pressed, res = aegisub.dialog.display(dialog_config,{"Convert","Shift","Cancel"},{cancel='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Convert" then framerate(subs, sel) end if pressed=="Shift" then shiift(subs, sel) end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, convertfps) -- This turns your \fad tag into \t\alpha, letting you add accel for the fade in and fade out. -- Supports present alpha tags. Supports multiple alpha tags in line (at least it should). -- If there already is an alpha transform, expect things to break. -- You can also fade to/from any colour. Should support inline \c and \3c tags. -- Does NOT support \2c, \4c, \1a, \2a, \3a, \4a. script_name="Turn fade into transform" script_description="Turn fade into alpha transform" script_author="unanimated" script_version="1.7" include("karaskel.lua") function fadalpha(subs, sel) for z, i in ipairs(sel) do local line=subs[i] local text=subs[i].text styleref=stylechk(subs,line.style) dur=line.end_time-line.start_time

col1=res.c1:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col2=res.c2:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&")

if text:match("\\fad%(") then fadin,fadout=text:match("\\fad%((%d+)%,(%d+)") primary=styleref.color1:gsub("H%x%x","H") pri=text:match("^{\\[^}]-\\c(&H%x+&)") if pri~=nil then primary=pri end outline=styleref.color3:gsub("H%x%x","H") out=text:match("^{\\[^}]-\\3c(&H%x+&)") if out~=nil then outline=out end border=styleref.outline bord=text:match("^{[^}]-\\bord([%d%.]+)") if bord~=nil then border=tonumber(bord) end text=text:gsub("\\1c","\\c")

kolora1="\\c"..col1 kolora3="\\3c"..col1 kolora="\\c"..col1.."\\3c"..col1 kolorb1="\\c"..col2 kolorb3="\\3c"..col2 kolorb="\\c"..col2.."\\3c"..col2 a00="\\alpha&H00&" aff="\\alpha&HFF&"

-- with alpha in line if text:match("\\alpha&H%x%x&") then

if fadin~="0" then -- fade from colour if res.crl then text=text:gsub("^({\\[^}]-)\\c&H%x+&","%1") text=text:gsub("^({\\[^}]-)\\3c&H%x+&","%1") text=text:gsub("^({\\[^}]-)}", "%1"..kolora.."\\t(0,"..fadin..","..res.inn..",\\c"..primary.."\\3c"..outline..")}") -- inline colour tags for t in text:gmatch("({\\[^}]-})") do if t~=text:match("^{\\[^}]-}") and t:match("\\[13]?c") then col1="" col3="" if t:match("\\c&") then col1=t:match("(\\c&H%x+&)") end if t:match("\\3c") then col3=t:match("(\\3c&H%x+&)") end t2=t:gsub("\\c&H%x+&",kolora1) t2=t2:gsub("\\3c&H%x+&",kolora3) t2=t2:gsub("({[^}]-)}","%1\\t(0,"..fadin..","..res.inn..","..col1..col3..")}") t=esc(t) text=text:gsub(t,t2) end end -- fade from alpha else if text:match("^{\\[^}]-\\alpha&H%x%x&") then text=text:gsub("^{(\\[^}]-)(\\alpha&H%x%x&)([^}]- )}","{%1%3\\alpha&HFF&\\t(0,"..fadin..","..res.inn..",%2)}") else text=text:gsub("^{(\\[^}]-)}","{%1"..aff.."\\t(0,"..fadin..","..res.inn..","..a00..")}") end -- inline alpha tags for t in text:gmatch("({\\[^}]-})") do if t~=text:match("^{\\[^}]-}") and t:match("\\alpha") then arfa=t:match("(\\alpha&H%x+&)") t2=t:gsub("\\alpha&H%x+&",aff) t2=t2:gsub("({[^}]-)}","%1\\t(0,"..fadin..","..res.inn..","..arfa..")}") t=esc(t) text=text:gsub(t,t2) end end end end

if fadout~="0" then -- fade to colour if res.clr then text=text:gsub("^({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.ut..","..kolorb..")}") -- inline colour tags for t in text:gmatch("({\\[^}]-})") do if t~=text:match("^{\\[^}]-}") and t:match("\\[13]?c") then t2=t:gsub("({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.ut..","..kolorb..")}") if not t:match("\\c&") then t2=t2:gsub("\\c&H%x+&","") end if not t:match("\\3c") then t2=t2:gsub("\\3c&H%x+&","") end t=esc(t) text=text:gsub(t,t2) end end -- fade to alpha else text=text:gsub("^({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.ut..","..aff..")}") -- inline alpha tags for t in text:gmatch("({\\[^}]-})") do if t~=text:match("^{\\[^}]-}") and t:match("\\alpha") then

t2=t:gsub("({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.ut..","..aff..")}") t=esc(t) text=text:gsub(t,t2) end end end end -- without alpha else

if fadin~="0" then -- fade from colour if res.crl then text=text:gsub("^({\\[^}]-)\\c&H%x+&","%1") text=text:gsub("^({\\[^}]-)\\3c&H%x+&","%1") text=text:gsub("^({\\[^}]-)}", "%1"..kolora.."\\t(0,"..fadin..","..res.inn..",\\c"..primary.."\\3c"..outline..")}") -- inline colour tags for t in text:gmatch("({\\[^}]-})") do if t~=text:match("^{\\[^}]-}") and t:match("\\[13]?c") then col1="" col3="" if t:match("\\c&") then col1=t:match("(\\c&H%x+&)") end if t:match("\\3c") then col3=t:match("(\\3c&H%x+&)") end t2=t:gsub("\\c&H%x+&",kolora1) t2=t2:gsub("\\3c&H%x+&",kolora3) t2=t2:gsub("({[^}]-)}","%1\\t(0,"..fadin..","..res.inn..","..col1..col3..")}") t=esc(t) text=text:gsub(t,t2) end end -- fade from alpha else text=text:gsub("^({\\[^}]-)}","%1"..aff.."\\t(0,"..fadin..","..res.inn..","..a00..")}") end end

if fadout~="0" then -- fade to colour if res.clr then text=text:gsub("^({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.ut..","..kolorb..")}") -- inline colour tags for t in text:gmatch("({\\[^}]-})") do if t~=text:match("^{\\[^}]-}") and t:match("\\[13]?c") then t2=t:gsub("({\\[^}]-)}","%1\\t("..dur-fadout..",0,"..res.ut..","..kolorb..")}") if not t:match("\\c&") then t2=t2:gsub("\\c&H%x+&","") end if not t:match("\\3c") then t2=t2:gsub("\\3c&H%x+&","") end t=esc(t) text=text:gsub(t,t2) end end -- fade to alpha else text=text:gsub("^({\\[^}]-)}","%1\\t(" .. dur-fadout ..",0," .. res.ut .. ","..aff..")}") end end end if border==0 then text=text:gsub("\\3c&H%x+&","") end if not text:match("\\fad%(0,0%)") then text=text:gsub("\\fad%(%d+,%d+%)","") end -- nuke the fade end line.text=text subs[i]=line end end function esc(str) str=str :gsub("%%","%%%%") :gsub("%(","%%%(") :gsub("%)","%%%)") :gsub("%[","%%%[") :gsub("%]","%%%]") :gsub("%.","%%%.") :gsub("%*","%%%*") :gsub("%-","%%%-") :gsub("%+","%%%+") :gsub("%?","%%%?") return str end function stylechk(subs,stylename) for i=1, #subs do if subs[i].class=="style" then local st=subs[i] if stylename==st.name then styleref=st end end end return styleref end function konfig(subs, sel) dialog_config= { {x=0,y=0,width=3,height=1,class="label",label="This will turn \\fad into \\t\\alpha",}, {x=0,y=1,width=1,height=1,class="label",label="accel in:",}, {x=0,y=2,width=1,height=1,class="label",label="accel out:",}, {x=1,y=1,width=2,height=1,class="floatedit",name="inn",value=1}, {x=1,y=2,width=2,height=1,class="floatedit",name="ut",value=1}, {x=0,y=3,width=3,height=1,class="label",label=" <1 starts fast, ends slow \n >1 starts slow, ends fast",}, {x=0,y=4,width=2,height=1,class="checkbox",name="crl",label="fade from: ",value=false}, {x=0,y=5,width=2,height=1,class="checkbox",name="clr",label="fade to:",value=false}, {x=2,y=4,width=1,height=1,class="color",name="c1"}, {x=2,y=5,width=1,height=1,class="color",name="c2"}, } pressed, res=aegisub.dialog.display(dialog_config,{"Transform","Cancel"},{ok='Transform',close='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Transform" then fadalpha(subs, sel) end end function fade2alpha(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, fade2alpha) -- Transforms a sign back and forth between two states in specified intervals, -- for example making the sign grow larger and smaller repeatedly each 600ms. script_name="Back and Forth Transform" script_description="Transforms between two sets of tags in specified intervals" script_author="unanimated" script_version="1.01" function tra(subs, sel) for z, i in ipairs(sel) do line=subs[i] text=line.text int=res.int -- get results from user input tags1=res.intag tags2=res.outag dur=line.end_time-line.start_time count=math.ceil(dur/int) t=1 tin=0 tout=tin+int if text:match("^{\\")==nil then text="{\\}"..text end -- add {\} if line has no tags text=text:gsub("^({\\[^}]*)}","%1"..tags1.."}") -- write initial tags -- main function while t<=math.ceil(count/2) do text=text:gsub("^({\\[^}]*)}","%1\\t("..tin..","..tout..","..tags2..")}") if tin+int0 then tra(subs, sel) end end function baf(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, baf) -- Makes text appear letter by letter or word by word in specified intervals. -- Might be buggy under various circumstances... script_name = "Show text letter by letter" script_description = "Inserts \ko tags before letters or words to make them appear one by one" script_author = "unanimated, lyger" script_version = "1.1" function koko_da(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text tekst1 = text:match("^([^{]*)") if res.word==false then --letter for text2 in text:gmatch("}([^{]*)") do text2m=text2:gsub("([%w%s%.,%?%!])","{\\ko"..res.ko.."}%1") text2=escape_string(text2) text=text:gsub(text2,text2m) end if tekst1~=nil then tekst1m=tekst1:gsub("([%w%s%.,%?%!])","{\\ko"..res.ko.."}%1") tekst1=escape_string(tekst1) text=text:gsub(tekst1,tekst1m) end else --word for text2 in text:gmatch("}([^{]*)") do text2m=text2:gsub("([%w\']+)","{\\ko"..res.ko.."}%1") text2=escape_string(text2) text=text:gsub(text2,text2m) end if tekst1~=nil then tekst1m=tekst1:gsub("([%w\']+)","{\\ko"..res.ko.."}%1") tekst1=escape_string(tekst1) text=text:gsub(tekst1,tekst1m) end end if text:match("^{")==nil then text=text:gsub("^","{\\ko"..res.ko.."}") end text=text:gsub("^{","{\\2a&HFF&") text=text:gsub("\\({\\ko[%d]+})N","\\N%1") text=text:gsub("\\ko[%d]+(\\ko[%d]+)","%1") line.text = text subs[i] = line end end function escape_string(str) str=str:gsub("%%","%%%%") str=str:gsub("%(","%%%(") str=str:gsub("%)","%%%)") str=str:gsub("%[","%%%[") str=str:gsub("%]","%%%]") str=str:gsub("%.","%%%.") str=str:gsub("%*","%%%*") str=str:gsub("%-","%%%-") str=str:gsub("%+","%%%+") str=str:gsub("%?","%%%?") return str end function konfig(subs, sel) dialog_config= { {x=0,y=0,width=2,height=1,class="label",label="Make text appear over time...",}, {x=0,y=1,width=1,height=1,class="label",label="Letter by letter or:",}, {x=1,y=1,width=1,height=1,class="checkbox",name="word",label="word by word",value=false}, {x=0,y=2,width=1,height=1,class="label",label="Interval for \\ko:",}, {x=1,y=2,width=1,height=1,class="floatedit",name="ko",value="8",}, {x=0,y=3,width=2,height=1,class="label",label="[Doesn't work with \\shad. '10' = 100ms.]",}, } pressed, res = aegisub.dialog.display(dialog_config,{"KO","Cancel"}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="KO" then koko_da(subs, sel) end end function ko(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, ko) -- a bunch of things that usually help me with song styling -- transforms include counting time from the end, like doing a transform for the last 500ms of the line -- inserting tags before last character is preparing the line for "gradiant by character" -- splitting line into 3 parts is useful when you want to apply different effects to the start and end, -- since long lines with \t tend to lag even if the actual transform covers only a short part of it script_name = "Song Styler" script_description = "Song Styler" script_author = "unanimated" script_version = "1.1" function tf(subs, sel) for z, i in ipairs(sel) do local line = subs[i] local text = subs[i].text

-- get colours from input col1=res["c1"] col1=col1:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col3=res["c3"] col3=col3:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col4=res["c4"] col4=col4:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col2=res["c2"] col2=col2:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&")

if text:match("^{\\")==nil then text="{\\}"..text end -- add {\} if line has no tags

-- transforms

dura=line.end_time-line.start_time

tstart=res.start tend=res.endd if res.tinend then tstart=dura-res.start end if res.toutend then tend=dura-res.endd end

text=text:gsub("^({\\[^}]*)}","%1".."\\t("..tstart..","..tend..","..res.accel..",alltagsgohere)}") transform=""

if res["bord1"] then transform=transform.."\\bord"..res["bord2"] end if res["shad1"] then transform=transform.."\\shad"..res["shad2"] end if res["blur1"] then transform=transform.."\\blur"..res["blur2"] end if res["k1"] then transform=transform.."\\c"..col1 end if res["k2"] then transform=transform.."\\2c"..col2 end if res["k3"] then transform=transform.."\\3c"..col3 end if res["k4"] then transform=transform.."\\4c"..col4 end if res.moretags~="\\" then transform=transform..res["moretags"] end text=text:gsub("alltagsgohere",transform) text=text:gsub("\\t%(0,0,1,","\\t(")

text=text:gsub("\\\\","\\") text=text:gsub("\\}","}") text=text:gsub("{}","") line.text = text subs[i] = line end end function grad(subs, sel) for z, i in ipairs(sel) do local line = subs[i] local text = subs[i].text

-- get colours from input col1=res["c1"] col1=col1:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col3=res["c3"] col3=col3:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col4=res["c4"] col4=col4:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col2=res["c2"] col2=col2:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&")

-- tags before last character

endtags="" if res["bord1"] then endtags=endtags.."\\bord"..res["bord2"] end if res["shad1"] then endtags=endtags.."\\shad"..res["shad2"] end if res["blur1"] then endtags=endtags.."\\blur"..res["blur2"] end if res["k1"] then endtags=endtags.."\\c"..col1 end if res["k2"] then endtags=endtags.."\\2c"..col2 end if res["k3"] then endtags=endtags.."\\3c"..col3 end if res["k4"] then endtags=endtags.."\\4c"..col4 end if res.moretags~="\\" then endtags=endtags..res["moretags"] end if text:match("}$") then text=text:gsub("([^}]{[^}]-})$","{"..endtags.."}%1") else text=text:gsub("([^}])$","{"..endtags.."}%1") end

text=text:gsub("\\\\","\\") text=text:gsub("\\}","}") text=text:gsub("{}","") line.text = text subs[i] = line end end function split(subs, sel) for i=#sel,1,-1 do line = subs[sel[i]] start=line.start_time -- start time endt=line.end_time -- end time effect=line.effect

-- line 3 line3=line line3.start_time=endt-res.split2 line3.effect=effect.." pt.3" if line3.start_time~=line3.end_time then subs.insert(sel[i]+1,line3) end

-- line 2 line2=line line2.start_time=start+res.split1 line2.end_time=endt-res.split2 line2.effect=effect.." pt.2" subs.insert(sel[i]+1,line2)

-- line 1 line.start_time=start line.end_time=start+res.split1 line.effect=effect.." pt.1"

subs[sel[i]] = line if line.start_time==line.end_time then subs.delete(sel[i]) end end end function sonconfig(subs, sel) dialog_config= { {x=0,y=0,width=1,height=1,class="label",label="", },

{x=0,y=1,width=1,height=1,class="checkbox",name="bord1",label="\\bord",value=false }, {x=0,y=2,width=1,height=1,class="checkbox",name="shad1",label="\\shad",value=false }, {x=0,y=3,width=1,height=1,class="checkbox",name="blur1",label="\\blur",value=false },

{x=1,y=1,width=1,height=1,class="floatedit",name="bord2",min=0 }, {x=1,y=2,width=1,height=1,class="floatedit",name="shad2",min=0 }, {x=1,y=3,width=1,height=1,class="floatedit",name="blur2",value=0.6,min=0 },

{x=2,y=1,width=1,height=1,class="checkbox",name="k1",label="Primary:",value=false }, {x=2,y=2,width=1,height=1,class="checkbox",name="k3",label="Border:",value=false }, {x=2,y=3,width=1,height=1,class="checkbox",name="k4",label="Shadow:",value=false }, {x=2,y=4,width=1,height=1,class="checkbox",name="k2",label="Secondary:",value=false },

{x=3,y=1,width=1,height=1,class="color",name="c1" }, {x=3,y=2,width=1,height=1,class="color",name="c3" }, {x=3,y=3,width=1,height=1,class="color",name="c4" }, {x=3,y=4,width=1,height=1,class="color",name="c2" },

{x=0,y=4,width=1,height=1,class="label",label="Transform:", }, {x=0,y=5,width=1,height=1,class="label",label="\\t start"}, {x=0,y=6,width=1,height=1,class="label",label="\\t end"}, {x=0,y=7,width=1,height=1,class="label",label="accel"}, {x=1,y=5,width=1,height=1,class="floatedit",name="start",value="0" }, {x=1,y=6,width=1,height=1,class="floatedit",name="endd",value="0" }, {x=1,y=7,width=1,height=1,class="floatedit",name="accel",value="1" },

{x=2,y=5,width=2,height=1,class="checkbox",name="tinend",label="count from end", hint="if a line is 3000ms and you set 500, transform wil start at 2500",value=false }, {x=2,y=6,width=2,height=1,class="checkbox",name="toutend",label="count from end", hint="if a line is 3000ms and you set 500, transform wil end at 2500",value=false }, {x=2,y=7,width=1,height=1,class="label",label="<1 starts fast, ends slow"},

{x=0,y=8,width=1,height=1,class="label",label="more tags:",value=false}, {x=1,y=8,width=3,height=1,class="edit",name="moretags",value="\\" },

{x=0,y=9,width=1,height=1,class="label",label="Split line"}, {x=1,y=9,width=1,height=1,class="label",label="first part [ms]:"}, {x=1,y=10,width=1,height=1,class="label",label="third part [ms]:"}, {x=2,y=9,width=2,height=1,class="floatedit",name="split1",value="0"}, {x=2,y=10,width=2,height=1,class="floatedit",name="split2",value="0"},

{x=0,y=11,width=4,height=1,class="label",label="[Transform] applies selected tags to a transform",}, {x=0,y=12,width=4,height=1,class="label",label="[Insert before last] inserts tags before last letter in the line",}, {x=0,y=13,width=4,height=1,class="label",label="[Split] splits the line into 3 parts", }, } pressed, res = aegisub.dialog.display(dialog_config,{"Transform","Insert before last","Split","Cancel"},{cancel='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Transform" then tf(subs, sel) end if pressed=="Insert before last" then grad(subs, sel) end if pressed=="Split" then split(subs, sel) end end function songs(subs, sel) sonconfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, songs) -- deletes current content of the line and adds a mask with blur and no border -- keeps only \pos tag, if present -- you can choose from 5 shapes for the mask: square, rounded square, circle, equilateral triangle, right-angled triangle script_name = "Add mask" script_description = "Adds a mask" script_author = "unanimated" script_version = "1.5" function addmask(subs, sel) for i=#sel,1,-1 do local l = subs[sel[i]] text=l.text l1=l l1.layer=l1.layer+1 if res.masknew then subs.insert(sel[i]+1,l1) end l.layer=l.layer-1

l.text=l.text:gsub(".*(\\pos%([%d%,%.]-%)).*","%1") if l.text:match("\\pos")==nil then l.text="" end if res["mask"]=="square" then l.text="{\\an5\\bord0\\blur1"..l.text.."\\p1}m 0 0 l 100 0 100 100 0 100" end if res["mask"]=="rounded square" then l.text="{\\an7\\bord0\\blur1"..l.text.."\\p1}m -100 -25 b -100 -92 -92 -100 -25 -100 l 25 -100 b 92 -100 100 - 92 100 -25 l 100 25 b 100 92 92 100 25 100 l -25 100 b -92 100 -100 92 -100 25 l -100 -25" end if res["mask"]=="circle" then l.text="{\\an7\\bord0\\blur1"..l.text.."\\p1}m -100 -100 b -45 -155 45 -155 100 -100 b 155 -45 155 45 100 100 b 46 155 -45 155 -100 100 b -155 45 -155 -45 -100 -100" end if res["mask"]=="equilateral triangle" then l.text="{\\an7\\bord0\\blur1"..l.text.."\\p1}m -122 70 l 122 70 l 0 -141" end if res["mask"]=="right-angled triangle" then l.text="{\\an7\\bord0\\blur1"..l.text.."\\p1}m -70 50 l 180 50 l -70 -100" end if l.text:match("\\pos")==nil then l.text=l.text:gsub("\\p1","\\pos(640,360)\\p1") end

subs[sel[i]] = l end end function maskonfig(subs, sel) dialog_config= { {x=0,y=0,width=1,height=1,class="label",label="Select the shape you want",}, {x=0,y=1,width=1,height=1,class="dropdown",name="mask", items={"square","rounded square","circle","equilateral triangle","right-angled triangle"},value="square"}, {x=0,y=3,width=1,height=1,class="checkbox",name="masknew",label="create mask on a new line",value=true}, } pressed, res = aegisub.dialog.display(dialog_config,{"Create Mask","Cancel"}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Create Mask" then addmask(subs, sel) end end function mask(subs, sel) maskonfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, mask) -- Alpha Text: Select timed lines with the same text. Alpha tags will be applied based on linebreaks in the GUI. -- Alpha Time: Select only one line with the full duration. Alpha tags will be applied, and line will be split and timed to even segments. -- @ - If the one selected line has @ markers, the line will be split by them without using the GUI. script_name="Alpha Timer" script_description="Alpha times shit" script_author="unanimated" script_version="1.1" function alfatxt(subs,sel) -- collect / check text for x, i in ipairs(sel) do text=subs[i].text if x==1 then alfatext=text:gsub("^{\\[^}]-}","") end if x~=1 then alfatext2=text:gsub("^{\\[^}]-}","") if alfatext2~=alfatext then aegisub.dialog.display({{class="label",label="Text must be the same for all selected lines",x=0,y=0,width=1,height=2}},{"OK"}) aegisub.cancel() end end end

if not alfatext:match("@") then -- GUI dialog_config={{x=0,y=0,width=5,height=8,class="textbox",name="alfa",value=alfatext }, {x=0,y=8,width=1,height=1,class="label", label="Break the text with 'Enter' the way it should be alpha-timed. (lines selected: "..#sel..")"},} pressed,res=aegisub.dialog.display(dialog_config,{"Alpha Text","Alpha Time","Cancel"},{ok='Alpha Text',close='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end data=res.alfa else data=alfatext:gsub("@","\n") pressed="Alpha Time" end -- sort data into a table altab={} data=data.."\n" for a in data:gmatch("(.-)\n") do if a~="" then table.insert(altab,a) end end

-- apply alpha text if pressed=="Alpha Text" then for x, i in ipairs(sel) do altxt="" for a=1,x do altxt=altxt..altab[a] end line=subs[i] text=line.text if altab[x]~=nil then tags=text:match("^{\\[^}]-}") text=text :gsub("^{\\[^}]-}","") :gsub(altxt,altxt.."{\\alpha&HFF&}") :gsub("({\\alpha&HFF&}.-){\\alpha&HFF&}","%1") :gsub("{\\alpha&HFF&}$","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") if tags~=nil then text=tags..text end end line.text=text subs[i]=line end end

-- apply alpha etxt + split line if pressed=="Alpha Time" then line=subs[sel[1]] start=line.start_time endt=line.end_time dur=endt-start f=dur/#altab for a=#altab,1,-1 do altxt="" altxt=altxt..altab[a] line.text=line.text:gsub("@","") line2=line tags=line2.text:match("^{\\[^}]-}") line2.text=line2.text :gsub("^{\\[^}]-}","") :gsub(altxt,altxt.."{\\alpha&HFF&}") :gsub("({\\alpha&HFF&}.-){\\alpha&HFF&}","%1") :gsub("{\\alpha&HFF&}$","") :gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") if tags~=nil then line2.text=tags..line2.text end line2.start_time=start+f*(a-1) line2.end_time=start+f+f*(a-1) subs.insert(sel[1]+1,line2) end subs.delete(sel[1]) end

aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, alfatxt) script_name = "Alignment" script_description = "aligns text" -- \an8 and the like script_author = "unanimated" script_version = "1.0" function align(subs, sel) chk=0 if ana["an1"]==true then num=1 chk=chk+1 end if ana["an2"]==true then num=2 chk=chk+1 end if ana["an3"]==true then num=3 chk=chk+1 end if ana["an4"]==true then num=4 chk=chk+1 end if ana["an5"]==true then num=5 chk=chk+1 end if ana["an6"]==true then num=6 chk=chk+1 end if ana["an7"]==true then num=7 chk=chk+1 end if ana["an8"]==true then num=8 chk=chk+1 end if ana["an9"]==true then num=9 chk=chk+1 end if chk>1 then aegisub.dialog.display({{class="label", label="Are you dumb or something? \nYou can only choose one.",x=0,y=0,width=1,height=2}},{"OK"}) end for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text if chk==1 then if text:match("\\an%d") then text = text:gsub("\\an%d","\\an"..num) else text = text:gsub("^","{\\an"..num.."}") text = text:gsub("({\\an%d)}{","%1") end end line.text = text subs[i] = line end aegisub.set_undo_point(script_name) return sel end function alignment(subs, sel) dialog_config= { {x=1,y=1,width=1,height=1,class="checkbox",name="an7",label="an7",value=false }, {x=3,y=1,width=1,height=1,class="checkbox",name="an8",label="an8",value=false }, {x=5,y=1,width=1,height=1,class="checkbox",name="an9",label="an9",value=false }, {x=1,y=3,width=1,height=1,class="checkbox",name="an4",label="an4",value=false }, {x=3,y=3,width=1,height=1,class="checkbox",name="an5",label="an5",value=false }, {x=5,y=3,width=1,height=1,class="checkbox",name="an6",label="an6",value=false }, {x=1,y=5,width=1,height=1,class="checkbox",name="an1",label="an1",value=false }, {x=3,y=5,width=1,height=1,class="checkbox",name="an2",label="an2",value=false }, {x=5,y=5,width=1,height=1,class="checkbox",name="an3",label="an3",value=false }, {x=0,y=6,width=7,height=1,class="label", }, }

pressed, ana = aegisub.dialog.display(dialog_config,{"Align","Cancel"})

if pressed=="Align" then align(subs, sel) end return sel end aegisub.register_macro(script_name, script_description, alignment) -- add \fax tag, possibly \fay [not recommended, as it tends to break stuff under some circumstances] script_name = "Add fax" script_description = "Add fax tag" script_author = "unanimated" script_version = "1.0" function fucks(subs, sel) for z, i in ipairs(sel) do local l = subs[i] if results["fay"]==false then l.text=l.text:gsub("\\fax[%d%.%-]-([\\}])","%1") if results["right"]==false then l.text="{\\fax" .. results["fax"] .. "}".. l.text else l.text="{\\fax" .. "-" .. results["fax"] .. "}".. l.text end l.text=l.text:gsub("^({\\[^}]-)}{\\","%1\\") else l.text=l.text:gsub("\\fay[%d%.%-]-([\\}])","%1") if results["right"]==false then l.text="{\\fay" .. results["fax"] .. "}".. l.text else l.text="{\\fay" .. "-" .. results["fax"] .. "}".. l.text end l.text=l.text:gsub("^({\\[^}]-)}{\\","%1\\") end subs[i] = l end end function faxconfig(subs, sel) dialog_config= { { class="label", x=0,y=0,width=3,height=1, label="0.05 = 3 degrees; 1 = 45 degrees", }, { class="edit",name="fax", x=1,y=1,width=1,height=1, value="0.05" }, { class="label", x=0,y=1,width=1,height=1, label="\\fax", }, { class="checkbox",name="right", x=0,y=2,width=2,height=1, label=" Lean to the right (like italics)", value=false }, { class="checkbox",name="fay", x=0,y=4,width=2,height=1, label="Use \\fay instead of \\fax", value=false }, { class="label", x=1,y=3,width=2,height=1, label="(...or upward for \\fay)", }, { class="label", x=1,y=5,width=2,height=1, label="(not recommended)", }, } pressed, results = aegisub.dialog.display(dialog_config,{"GO","Leave"}) if pressed=="Leave" then aegisub.cancel() end if pressed=="GO" then fucks(subs, sel) end end function fax(subs, sel) faxconfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, fax) -- This is for signs with border and fade, after you've used "Duplicate and Blur" -- It makes the bottom layer's primary colour transparent during the fade. script_name = "Bottom alpha" script_description = "sets alpha for bottom layer of a 2-layer sign" script_author = "unanimated" script_version = "1.0" include("karaskel.lua") function blpha(subs, sel) local meta,styles=karaskel.collect_head(subs,false) for z, i in ipairs(sel) do local line = subs[i] local text = subs[i].text karaskel.preproc_line(sub,meta,styles,line) if line.text:match("\\fad%(") then fadin,fadout = line.text:match("\\fad%((%d+)%,(%d+)") if fadin~="0" then text = text:gsub("^({\\[^}]-)}","%1\\1a&HFF&\\t(".. fadin-results["inn"] .."," .. fadin ..",\\1a&H00&)}") end if fadout~="0" then text = text:gsub("^({\\[^}]-)}","%1\\t(" .. line.duration-fadout .."," .. line.duration-fadout+results["ut"] .. ",\\1a&HFF&)}") end if fadin=="0" and fadout=="0" then aegisub.dialog.display( {{class="label",label="Some lines were skipped \nbecause they contain \\fad(0,0)",x=0,y=0,width=1,height=2}},{"OK"}) end end line.text = text subs[i] = line end end function konfig(subs, sel) dialog_config= { { x=0,y=1,width=1,height=1,class="label",label="ms to fade in:", }, { x=0,y=2,width=1,height=1,class="label",label="ms to fade out:", }, { x=0,y=0,width=2,height=1,class="label",label="fade for bottom layer of a 2-layer sign", }, { x=0,y=3,width=2,height=1,class="label",label="", }, { x=1,y=1,width=2,height=1,class="dropdown",name="inn",items={"0","45","80","120"},value="45" }, { x=1,y=2,width=2,height=1,class="dropdown",name="ut",items={"0","45","80","120"},value="45" }, } pressed, results = aegisub.dialog.display(dialog_config,{"Transform","Go away, please"}) if pressed=="Go away, please" then aegisub.cancel() end if pressed=="Transform" then blpha(subs, sel) end end function bottomalpha(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, bottomalpha) -- Create \t transforms. Type start/end time, acceleration, and tags you want to transform. script_name = "Add Animated Transform" script_description = "Adds Animated Transform(s)" script_author = "unanimated" script_version = "1.0" function trnsf(subs, sel) for z, i in ipairs(sel) do local line = subs[i] local text = subs[i].text text = "{transforrrm}" .. text text = text:gsub("{transforrrm}({\\[^}]*)}","%1transforrrm}") if results["tcheck"]==true then text = text:gsub("transforrrm", "\\t(" .. results["start"] .. "," .. results["end"] .. "," .. results["accel"] .. "," .. results["tags"] .. ")") else text = text:gsub("transforrrm","\\t(" .. results["tags"] .. ")") end line.text = text subs[i] = line end end function transconfig(subs, sel) dialog_config= { {x=0,y=5,width=1,height=1,class="label",label="Tags to put inside \\t():", }, {x=0,y=6,width=3,height=1,class="edit",name="tags",value="\\" }, {x=0,y=2,width=3,height=1,class="checkbox",name="tcheck",label="Enable times/acceleration (otherwise only \\t(\\tags))",value=true}, {x=0,y=4,width=1,height=1,class="floatedit",name="start",value="0" }, {x=1,y=4,width=1,height=1,class="floatedit",name="end",value="0" }, {x=2,y=4,width=1,height=1,class="floatedit",name="accel",value="1" }, {x=0,y=0,width=3,height=1,class="label",label="Set times and acceleration if you need them (or you can disable them)",}, {x=0,y=1,width=3,height=1,class="label",label="Type out tags in this form: \\bord5\\shad5\\frz90 etc.", }, {x=0,y=7,width=3,height=1,class="label",label="New transforms are always placed at the end of the first block of tags", }, {x=0,y=3,width=1,height=1,class="label",label="\\t start", }, {x=1,y=3,width=1,height=1,class="label",label="\\t end", }, {x=2,y=3,width=1,height=1,class="label",label="accel", }, } pressed, results = aegisub.dialog.display(dialog_config,{"Transform","Cancel"}) if pressed=="Cancel" then aegisub.cancel() end

if pressed=="Transform" then trnsf(subs, sel) end end function transform(subs, sel) transconfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, transform) script_name = "Teleporter" script_description = "Teleporter aka position/move/org/clip shifter" script_author = "unanimated" script_version = "1.3" -- added support for vectorial clips function teleport(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text xx=res.eks yy=res.wai

if res.pos then text=text:gsub("\\pos%(([%d%.%-]+)%,([%d%.%-]+)%)", function(a,b) return "\\pos(".. a+xx.. "," ..b+yy..")" end) end

if res.org then text=text:gsub("\\org%(([%d%.%-]+)%,([%d%.%-]+)%)", function(a,b) return "\\org(".. a+xx.. "," ..b+yy..")" end) end

if res.mov then text=text:gsub("\\move%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)", function(a,b,c,d) return "\\move("..a+xx.. "," ..b+yy.. "," ..c+xx.. "," ..d+yy end) end

if res.clip then text=text:gsub("clip%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)", function(a,b,c,d) return "clip("..a+xx.. "," ..b+yy.. "," ..c+xx.. "," ..d+yy end)

if text:match("clip%(m [%d%a%s%-]+%)") then ctext=text:match("clip%(m ([%d%a%s%-]+)%)") ctext2=ctext:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return a+xx.." "..b+yy end) ctext=ctext:gsub("%-","%%-") text=text:gsub(ctext,ctext2) end end

line.text = text subs[i] = line end end function konfig(subs, sel) dialog_config= { { x=0,y=0,width=1,height=1,class="label",label="Shift X by:"}, { x=0,y=1,width=1,height=1,class="label",label="Shift Y by:"}, { x=1,y=0,width=2,height=1,class="floatedit",name="eks"}, { x=1,y=1,width=2,height=1,class="floatedit",name="wai"},

{ x=0,y=2,width=1,height=1,class="checkbox",name="pos",label="\\pos",value=true }, { x=0,y=3,width=1,height=1,class="checkbox",name="mov",label="\\move",value=true }, { x=1,y=2,width=1,height=1,class="checkbox",name="clip",label="\\[i]clip",value=true }, { x=1,y=3,width=1,height=1,class="checkbox",name="org",label="\\org",value=true }, } buttons={"Teleport","Stay here"} pressed, res = aegisub.dialog.display(dialog_config,buttons,{cancel='Stay here'}) if pressed=="Teleport" then teleport(subs, sel) end end function teleporter(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, teleporter) -- Unlike with Teleporter, you set target position, not position difference, but all your lines must have the same position. script_name = "Gradient Shifter" script_description = "Gradient Shifter" script_author = "unanimated" script_version = "1.0" function shift(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text xx=res.eks yy=res.wai p1,p2=text:match("\\pos%(([%d%.%-]+)%,([%d%.%-]+)%)") if p1==nil then p1,p2=text:match("\\move%(([%d%.%-]+)%,([%d%.%-]+)") end xxx=xx-p1 yyy=yy-p2

if res.pos then text=text:gsub("\\pos%(([%d%.%-]+)%,([%d%.%-]+)%)","\\pos("..xx.."," ..yy..")" ) end

if res.org then text=text:gsub("\\org%(([%d%.%-]+)%,([%d%.%-]+)%)", function(a,b) return "\\org(".. a+xxx.. "," ..b+yyy..")" end) end

if res.mov then text=text:gsub("\\move%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)", function(a,b,c,d) return "\\move("..a+xxx.. "," ..b+yyy.. "," ..c+xxx.. "," ..d+yyy end) end

if res.clip then text=text:gsub("clip%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)", function(a,b,c,d) return "clip("..a+xxx.. "," ..b+yyy.. "," ..c+xxx.. "," ..d+yyy end) end

line.text = text subs[i] = line end end function konfig(subs, sel) dialog_config= { { x=0,y=0,width=1,height=1,class="label",label="Target X:"}, { x=0,y=1,width=1,height=1,class="label",label="Target Y:"}, { x=1,y=0,width=2,height=1,class="floatedit",name="eks"}, { x=1,y=1,width=2,height=1,class="floatedit",name="wai"},

{ x=0,y=2,width=1,height=1,class="checkbox",name="pos",label="\\pos",value=true }, { x=0,y=3,width=1,height=1,class="checkbox",name="mov",label="\\move",value=true }, { x=1,y=2,width=1,height=1,class="checkbox",name="clip",label="\\[i]clip",value=true }, { x=1,y=3,width=1,height=1,class="checkbox",name="org",label="\\org",value=true }, } buttons={"Shift","Nope"} pressed, res = aegisub.dialog.display(dialog_config,buttons) if pressed=="Shift" then shift(subs, sel) end end function gradshift(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, gradshift) -- Copies coordinates (whatever's in parentheses) from first selected line to the rest of selected lines. -- Works with \pos, \move, \org, \clip, \iclip, \t\clip, \t\iclip. script_name = "Copy Coordinates" script_description = "Copy pos, move, org, clip coordinates from first line to others" script_author = "unanimated" script_version = "1.2" function replace(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text if not text:match("^{\\") then text=text:gsub("^","{\\}") end

if res["pos"]==true then if x==1 and text:match("\\pos") then posi=text:match("\\pos%(([^%)]-)%)") end if x~=1 and text:match("\\pos") and posi~=nil then text=text:gsub("\\pos%([^%)]-%)","\\pos%("..posi.."%)") end if x~=1 and not text:match("\\pos") and posi~=nil and res.cre then text=text:gsub("^{\\","{\\pos%("..posi.."%)\\") end end

if res["mov"]==true then if x==1 and text:match("\\move") then move=text:match("\\move%(([^%)]-)%)") end if x~=1 and text:match("\\move") and move~=nil then text=text:gsub("\\move%([^%)]-%)","\\move%("..move.."%)") end if x~=1 and not text:match("\\move") and move~=nil and res.cre then text=text:gsub("^{\\","{\\move%("..move.."%)\\") end end

if res["org"]==true then if x==1 and text:match("\\org") then orig=text:match("\\org%(([^%)]-)%)") end if x~=1 and text:match("\\org") and orig~=nil then text=text:gsub("\\org%([^%)]-%)","\\org%("..orig.."%)") end if x~=1 and not text:match("\\org") and orig~=nil and res.cre then text=text:gsub("^({\\[^}]*)}","%1\\org%("..orig.."%)}") end end

if res["clip"]==true then if x==1 and text:match("\\i?clip") then klip=text:match("\\i?clip%(([^%)]-)%)") end if x~=1 and text:match("\\i?clip") and klip~=nil then text=text:gsub("\\(i?clip)%([^%)]-%)","\\%1%("..klip.."%)") end if x~=1 and not text:match("\\i?clip") and klip~=nil and res.cre then text=text:gsub("^({\\[^}]*)}","%1\\clip%("..klip.."%)}") end end

if res["tclip"]==true then if x==1 and text:match("\\t%([%d%.%,]*\\i?clip") then tklip=text:match("\\t%([%d%.%,]*\\i?clip%(([^%)]-)%)") end if x~=1 and text:match("\\i?clip") and tklip~=nil then text=text:gsub("\\t%(([%d%.%,]*)\\(i?clip)%([^%)]-%)","\\t%(%1\\%2%("..tklip.."%)") end if x~=1 and not text:match("\\t%([%d%.%,]*\\i?clip") and tklip~=nil and res.cre then text=text:gsub("^({\\[^}]*)}","%1\\t%(\\clip%("..tklip.."%)%)}") end end

text=text :gsub("\\\\","\\") :gsub("\\}","}") :gsub("{}","")

line.text = text subs[i] = line end posi, move, orig, klip, tklip=nil end function konfig(subs, sel) dialog_config= { { x=0,y=0,width=2,height=1,class="label",label="Copy from first line to the rest"}, { x=0,y=1,width=1,height=1,class="checkbox",name="pos",label="\\pos",value=true }, { x=0,y=2,width=1,height=1,class="checkbox",name="mov",label="\\move",value=true }, { x=0,y=3,width=1,height=1,class="checkbox",name="org",label="\\org",value=true }, { x=1,y=1,width=1,height=1,class="checkbox",name="clip",label="\\[i]clip",value=true }, { x=1,y=2,width=1,height=1,class="checkbox",name="tclip",label="\\t(\\[i]clip)",value=true }, { x=0,y=4,width=2,height=1,class="checkbox",name="cre",label="create tags if missing",value=true },

} buttons={"Copy","Cancel"} pressed, res = aegisub.dialog.display(dialog_config,buttons)

if pressed=="Copy" then replace(subs, sel) end end function copytags(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, copytags) --[[ Instructions

Position: Align X means all selected \pos tags will have the same given X coordinate. Same with Align Y for Y. useful for multiple signs on screen that need to be aligned horizontally/vertically or mocha signs that should move horizontally/vertically.

Move: horizontal means y2 will be the same as y1 so that the sign moves in a straight horizontal manner. Same principle for vertical. Transmove: this is the real deal here. Main function: create \move from two lines with \pos. Duplicate your line and position the second one where you want the \move the end. Script will create \move from the two positions. Second line will be deleted by default; it's there just so you can comfortably set the final position. Extra function: to make this a lot more awesome, this can create transforms. Not only is the second line used for \move coordinates, but also for transforms. Any tag on line 2 that's different from line 1 will be used to create a transform on line 1. So for a \move with transforms you can set the initial sign and then the final sign while everything is static. You can time line 2 to just the last frame. The script only uses timecodes from line 1. Text from line 2 is also ignored (assumed to be same as line 1). You can time line 2 to start after line 1 and check "keep line 2." That way line 1 transforms into line 2 and the sign stays like that for the duration of line 2. "Rotation shortcut" - like with fbf-transform, this ensures that transforms of rotations will go the shortest way, thus going only 4 degrees from 358 to 2 and not 356 degrees around. If the \pos is the same on both lines, only transforms will be applied.

Modifications: 'round numbers' rounds coordinates for pos, move, org and clip depending on the 'Round' submenu. 'reverse move' reverses the direction of \move.

--]] script_name = "Position Adjuster" script_description = "Does things and stuff" script_author = "unanimated" script_version = "1.2" function positron(subs,sel) ps=res.pos for x, i in ipairs(sel) do local line = subs[i] local text=line.text if x==1 and not text:match("\\pos") then aegisub.dialog.display({{class="label", label="No \\pos tag in the first line.",x=0,y=0,width=1,height=2}},{"OK"}) aegisub.cancel() end

if x==1 and res.first then xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") if res.posi=="Align X" then ps=xx else ps=yy end end

if text:match("\\pos") then if res.posi=="Align X" then text=text:gsub("\\pos%(([%d%.%-]+)%,([%d%.%-]+)%)","\\pos("..ps..",%2)") else text=text:gsub("\\pos%(([%d%.%-]+)%,([%d%.%-]+)%)","\\pos(%1,"..ps..")") end end line.text=text subs[i] = line end end function moove(subs, sel) for i=#sel,1,-1 do local line = subs[sel[i]] local text=line.text

if res.move=="transmove" and sel[i]<#subs then

start=line.start_time -- start time endt=line.end_time -- end time nextline=subs[sel[i]+1] text2=nextline.text

ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame

keyframes=aegisub.keyframes() -- keyframes table startf=ms2fr(start) -- startframe endf=ms2fr(endt) -- endframe start2=fr2ms(startf) endt2=fr2ms(endf-1) tim=fr2ms(1) movt1=start2-start+tim -- first timecode in \move movt2=endt2-start+tim -- second timecode in \move movt=movt1..","..movt2

-- failcheck if not text:match("\\pos") or not text2:match("\\pos") then aegisub.dialog.display({{class="label",label="Missing \\pos tags.",x=0,y=0,width=1,height=2}},{"OK"}) aegisub.cancel() end

-- move p1=text:match("\\pos%(([^%)]+)%)") p2=text2:match("\\pos%(([^%)]+)%)") if p2~=p1 then text=text:gsub("\\pos%(([^%)]+)%)","\\move(%1,"..p2..","..movt..")") end

-- transforms tf=""

-- fstuff if text2:match("\\fs[%d%.]+") then fs2=text2:match("(\\fs[%d%.]+)") if text:match("\\fs[%d%.]+") then fs1=text:match("(\\fs[%d%.]+)") else fs1="" end if fs1~=fs2 then tf=tf..fs2 end end if text2:match("\\fsp[%d%.%-]+") then fsp2=text2:match("(\\fsp[%d%.%-]+)") if text:match("\\fsp[%d%.%-]+") then fsp1=text:match("(\\fsp[%d%.%-]+)") else fsp1="" end if fsp1~=fsp2 then tf=tf..fsp2 end end if text2:match("\\fscx[%d%.]+") then fscx2=text2:match("(\\fscx[%d%.]+)") if text:match("\\fscx[%d%.]+") then fscx1=text:match("(\\fscx[%d%.]+)") else fscx1="" end if fscx1~=fscx2 then tf=tf..fscx2 end end if text2:match("\\fscy[%d%.]+") then fscy2=text2:match("(\\fscy[%d%.]+)") if text:match("\\fscy[%d%.]+") then fscy1=text:match("(\\fscy[%d%.]+)") else fscy1="" end if fscy1~=fscy2 then tf=tf..fscy2 end end -- blur border shadow if text2:match("\\blur[%d%.]+") then blur2=text2:match("(\\blur[%d%.]+)") if text:match("\\blur[%d%.]+") then blur1=text:match("(\\blur[%d%.]+)") else blur1="" end if blur1~=blur2 then tf=tf..blur2 end end if text2:match("\\bord[%d%.]+") then bord2=text2:match("(\\bord[%d%.]+)") if text:match("\\bord[%d%.]+") then bord1=text:match("(\\bord[%d%.]+)") else bord1="" end if bord1~=bord2 then tf=tf..bord2 end end if text2:match("\\shad[%d%.]+") then shad2=text2:match("(\\shad[%d%.]+)") if text:match("\\shad[%d%.]+") then shad1=text:match("(\\shad[%d%.]+)") else shad1="" end if shad1~=shad2 then tf=tf..shad2 end end -- colours if text2:match("\\1?c&H%x+&") then c12=text2:match("(\\1?c&H%x+&)") if text:match("\\1?c&H%x+&") then c11=text:match("(\\1?c&H%x+&)") else c11="" end if c11~=c12 then tf=tf..c12 end end if text2:match("\\2c&H%x+&") then c22=text2:match("(\\2c&H%x+&)") if text:match("\\2c&H%x+&") then c21=text:match("(\\2c&H%x+&)") else c21="" end if c21~=c22 then tf=tf..c22 end end if text2:match("\\3c&H%x+&") then c32=text2:match("(\\3c&H%x+&)") if text:match("\\3c&H%x+&") then c31=text:match("(\\3c&H%x+&)") else c31="" end if c31~=c32 then tf=tf..c32 end end if text2:match("\\4c&H%x+&") then c42=text2:match("(\\4c&H%x+&)") if text:match("\\4c&H%x+&") then c41=text:match("(\\4c&H%x+&)") else c41="" end if c41~=c42 then tf=tf..c42 end end -- alphas if text2:match("\\alpha&H%x+&") then alpha2=text2:match("(\\alpha&H%x+&)") if text:match("\\alpha&H%x+&") then alpha1=text:match("(\\alpha&H%x+&)") else alpha1="" end if alpha1~=alpha2 then tf=tf..alpha2 end end if text2:match("\\1a&H%x+&") then a12=text2:match("(\\1a&H%x+&)") if text:match("\\1a&H%x+&") then a11=text:match("(\\1a&H%x+&)") else a11="" end if a11~=a12 then tf=tf..a12 end end if text2:match("\\2a&H%x+&") then a22=text2:match("(\\2a&H%x+&)") if text:match("\\2a&H%x+&") then a21=text:match("(\\2a&H%x+&)") else a21="" end if a21~=a22 then tf=tf..a22 end end if text2:match("\\3a&H%x+&") then a32=text2:match("(\\3a&H%x+&)") if text:match("\\3a&H%x+&") then a31=text:match("(\\3a&H%x+&)") else a31="" end if a31~=a32 then tf=tf..a32 end end if text2:match("\\4a&H%x+&") then a42=text2:match("(\\4a&H%x+&)") if text:match("\\4a&H%x+&") then a41=text:match("(\\4a&H%x+&)") else a41="" end if a41~=a42 then tf=tf..a42 end end -- rotations if text2:match("\\frz[%d%.%-]+") then frz2=text2:match("(\\frz[%d%.%-]+)") zz2=tonumber(text2:match("\\frz([%d%.%-]+)")) if text:match("\\frz[%d%.%-]+") then frz1=text:match("(\\frz[%d%.%-]+)") zz1=tonumber(text:match("\\frz([%d%.%-]+)")) else frz1="" zz1="0" end if frz1~=frz2 then if res.rot and math.abs(zz2-zz1)>180 then if zz2>zz1 then zz2=zz2-360 frz2="\\frz"..zz2 else zz1=zz1-360 text=text:gsub("\\frz[%d%.%-]+","\\frz"..zz1) end end tf=tf..frz2 end end if text2:match("\\frx[%d%.%-]+") then frx2=text2:match("(\\frx[%d%.%-]+)") xx2=tonumber(text2:match("\\frx([%d%.%-]+)")) if text:match("\\frx[%d%.%-]+") then frx1=text:match("(\\frx[%d%.%-]+)") xx1=tonumber(text:match("\\frx([%d%.%-]+)")) else frx1="" xx1="0" end if frx1~=frx2 then if res.rot and math.abs(xx2-xx1)>180 then if xx2>xx1 then xx2=xx2-360 frx2="\\frx"..xx2 else xx1=xx1-360 text=text:gsub("\\frx[%d%.%-]+","\\frx"..xx1) end end tf=tf..frx2 end end if text2:match("\\fry[%d%.%-]+") then fry2=text2:match("(\\fry[%d%.%-]+)") yy2=tonumber(text2:match("\\fry([%d%.%-]+)")) if text:match("\\fry[%d%.%-]+") then fry1=text:match("(\\fry[%d%.%-]+)") yy1=tonumber(text:match("\\fry([%d%.%-]+)")) else fry1="" yy1="0" end if fry1~=fry2 then if res.rot and math.abs(yy2-yy1)>180 then if yy2>yy1 then yy2=yy2-360 fry2="\\fry"..yy2 else yy1=yy1-360 text=text:gsub("\\fry[%d%.%-]+","\\fry"..yy1) end end tf=tf..fry2 end end -- shearing if text2:match("\\fax[%d%.%-]+") then fax2=text2:match("(\\fax[%d%.%-]+)") if text:match("\\fax[%d%.%-]+") then fax1=text:match("(\\fax[%d%.%-]+)") else fax1="" end if fax1~=fax2 then tf=tf..fax2 end end if text2:match("\\fay[%d%.%-]+") then fay2=text2:match("(\\fay[%d%.%-]+)") if text:match("\\fay[%d%.%-]+") then fay1=text:match("(\\fay[%d%.%-]+)") else fay1="" end if fay1~=fay2 then tf=tf..fay2 end end -- apply transform if tf~="" then text=text:gsub("^({\\[^}]-)}","%1\\t("..movt..","..tf..")}") end

-- delete line 2 if res.keep==false then subs.delete(sel[i]+1) end

end -- end of transmove

if res.move=="horizontal" then text=text:gsub("\\move%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%- ]+)","\\move(%1,%2,%3,%2") end if res.move=="vertical" then text=text:gsub("\\move%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%- ]+)","\\move(%1,%2,%1,%4") end

line.text=text subs[sel[i]] = line end end function modify(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text=line.text

if res.mod=="round numbers" then if text:match("\\pos") and res.rnd=="all" or text:match("\\pos") and res.rnd=="pos" then px,py=text:match("\\pos%(([%d%.]+),([%d%.]+)%)") if px-math.floor(px)>=0.5 then px=math.ceil(px) else px=math.floor(px) end if py-math.floor(py)>=0.5 then py=math.ceil(py) else py=math.floor(py) end text=text:gsub("\\pos%([%d%.]+,[%d%.]+%)","\\pos("..px..","..py..")") end if text:match("\\org") and res.rnd=="all" or text:match("\\org") and res.rnd=="org" then ox,oy=text:match("\\org%(([%d%.]+),([%d%.]+)%)") if ox-math.floor(ox)>=0.5 then ox=math.ceil(ox) else ox=math.floor(ox) end if oy-math.floor(oy)>=0.5 then oy=math.ceil(oy) else oy=math.floor(oy) end text=text:gsub("\\org%([%d%.]+,[%d%.]+%)","\\org("..ox..","..oy..")") end if text:match("\\move") and res.rnd=="all" or text:match("\\move") and res.rnd=="move" then mo1,mo2,mo3,mo4=text:match("\\move%(([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)") if mo1-math.floor(mo1)>=0.5 then mo1=math.ceil(mo1) else mo1=math.floor(mo1) end if mo2-math.floor(mo2)>=0.5 then mo2=math.ceil(mo2) else mo2=math.floor(mo2) end if mo3-math.floor(mo3)>=0.5 then mo3=math.ceil(mo3) else mo3=math.floor(mo3) end if mo4-math.floor(mo4)>=0.5 then mo4=math.ceil(mo4) else mo4=math.floor(mo4) end text=text:gsub("\\move%([%d%.]+,[%d%.]+,[%d%.]+,[%d%.]+","\\move("..mo1..","..mo2..","..mo3..","..mo4) end if text:match("\\clip%([%d%.]+,[%d%.]+,[%d%.]+,[%d%.]+") and res.rnd=="all" or text:match("\\clip%([%d%.]+,[%d%.]+,[%d%.]+,[%d%.]+") and res.rnd=="clip" then mo1,mo2,mo3,mo4=text:match("\\i?clip%(([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)") if mo1-math.floor(mo1)>=0.5 then mo1=math.ceil(mo1) else mo1=math.floor(mo1) end if mo2-math.floor(mo2)>=0.5 then mo2=math.ceil(mo2) else mo2=math.floor(mo2) end if mo3-math.floor(mo3)>=0.5 then mo3=math.ceil(mo3) else mo3=math.floor(mo3) end if mo4-math.floor(mo4)>=0.5 then mo4=math.ceil(mo4) else mo4=math.floor(mo4) end text=text:gsub("(\\i?clip)%([%d%.]+,[%d%.]+,[%d%.]+,[%d%.]+","%1("..mo1..","..mo2..","..mo3..","..mo4) end end

if res.mod=="reverse move" then text=text:gsub("\\move%(([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)","\\move(%3,%4,%1,%2") end

line.text=text subs[i] = line end end function gui(subs, sel) dialog_config= { {x=0,y=0,width=2,height=1,class="label",label="Position",}, {x=0,y=1,width=1,height=1,class="dropdown",name="posi",items={"Align X","Align Y"},value="Align X",}, {x=0,y=2,width=1,height=1,class="floatedit",name="pos",value=0}, {x=0,y=3,width=1,height=1,class="checkbox",name="first",label="use first line",value=false,},

{x=2,y=0,width=2,height=1,class="label",label="Move"},

{x=2,y=1,width=1,height=1,class="dropdown",name="move",items={"transmove","horizontal","vertical"},value="tran smove",}, {x=2,y=2,width=1,height=1,class="checkbox",name="keep",label="keep line 2",value=false,}, {x=2,y=3,width=3,height=1,class="checkbox",name="rot",label="rotation shortcut", hint="for transforms in 'transmove' mode",value=true,},

{x=4,y=0,width=2,height=1,class="label",label="Modifications:",}, {x=4,y=1,width=2,height=1,class="dropdown",name="mod",items={"round numbers","reverse move"},value="round numbers"}, {x=4,y=2,width=1,height=1,class="label",label="Round:",},

{x=5,y=2,width=1,height=1,class="dropdown",name="rnd",items={"all","pos","move","org","clip"},value="all"}, } pressed, res = aegisub.dialog.display(dialog_config, {"Position","Move","Mod","Cancel"},{cancel='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end

if pressed=="Position" then positron(subs, sel) end if pressed=="Move" then moove(subs, sel) end if pressed=="Mod" then modify(subs, sel) end end function posadjust(subs, sel) gui(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, posadjust) -- See description. Also, why the fuck has nobody written this yet? script_name = "Multicolour" script_description = "Apply colours to multiple lines" script_author = "unanimated" script_version = "1.0" function mc(subs, sel) for z, i in ipairs(sel) do line = subs[i] text = subs[i].text -- get colours from input col1=results["c1"] col1=col1:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col3=results["c3"] col3=col3:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col4=results["c4"] col4=col4:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") col2=results["c2"] col2=col2:gsub("#(%x%x)(%x%x)(%x%x)","&H%3%2%1&") -- set selected colours if text:match("^{\\")==nil then text="{\\}"..text end -- add {\} if line has no tags if results["k1"]==true then if text:match("^{[^}]*\\c&") then text=text:gsub("^({[^}]*\\c)(&H%x%x%x%x%x%x&)","%1"..col1) else text=text:gsub("^({\\[^}]*)}","%1\\c"..col1.."}") end end if results["k2"]==true then if text:match("^{[^}]*\\2c&") then text=text:gsub("^({[^}]*\\2c)(&H%x%x%x%x%x%x&)","%1"..col2) else text=text:gsub("^({\\[^}]*)}","%1\\2c"..col2.."}") end end if results["k3"]==true then if text:match("^{[^}]*\\3c&") then text=text:gsub("^({[^}]*\\3c)(&H%x%x%x%x%x%x&)","%1"..col3) else text=text:gsub("^({\\[^}]*)}","%1\\3c"..col3.."}") end end if results["k4"]==true then if text:match("^{[^}]*\\4c&") then text=text:gsub("^({[^}]*\\4c)(&H%x%x%x%x%x%x&)","%1"..col4) else text=text:gsub("^({\\[^}]*)}","%1\\4c"..col4.."}") end end text=text:gsub("{\\\\","{\\") -- clean up \\ line.text = text subs[i] = line end end function konfig(subs, sel) dialog_config= { { x=0,y=0,width=3,height=1,class="label",label="Check colours you want used", }, { x=0,y=1,width=1,height=1,class="checkbox",name="k1",label="Primary:",value=true }, { x=0,y=2,width=1,height=1,class="checkbox",name="k3",label="Border:",value=false }, { x=0,y=3,width=1,height=1,class="checkbox",name="k4",label="Shadow:",value=false }, { x=0,y=5,width=1,height=1,class="checkbox",name="k2",label="useless... (2c):",value=false },

{ x=1,y=1,width=1,height=1,class="color",name="c1" }, { x=1,y=2,width=1,height=1,class="color",name="c3" }, { x=1,y=3,width=1,height=1,class="color",name="c4" }, { x=1,y=5,width=1,height=1,class="color",name="c2" }, } pressed, results = aegisub.dialog.display(dialog_config,{"Apply","Cancel"}) if pressed=="Apply" then mc(subs, sel) end if pressed=="Cancel" then aegisub.cancel() end end function multicolour(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, multicolour) -- Creates transform tags for alpha. -- GUI lets you select alpha before and after transform, as well as start/end time and acceleration for the \t tag. -- Additional running of the script places the new transform at the end. -- If there already is an alpha tag, "Alpha in" will be ignored. script_name = "Transform alpha" script_description = "Creates transform tags for alpha" script_author = "unanimated" script_version = "1.2" function talpha(subs, sel) for z, i in ipairs(sel) do local line = subs[i] local text = subs[i].text text = "{transforrrm}" .. text text = text:gsub("{transforrrm}({\\[^}]*)}","%1transforrrm}") if line.text:match("^{.-\\alpha.-}") then if results["tcheck"]==true then text = text:gsub("transforrrm", "\\t(" .. results["start"] .. "," .. results["end"] .. "," .. results["accel"] .. "," .. "\\alpha&H" .. results["alphaout"] .. "&)") else text = text:gsub("transforrrm","\\t(" .. "\\alpha&H" .. results["alphaout"] .. "&)") end else if results["tcheck"]==true then text = text:gsub("transforrrm","\\alpha&H" .. results["alphain"] .. "&\\t(" .. results["start"] .. "," .. results["end"] .. "," .. results["accel"] .. "," .. "\\alpha&H" .. results["alphaout"] .. "&)") else text = text:gsub("transforrrm","\\alpha&H" .. results["alphain"] .. "&\\t(" .. "\\alpha&H" .. results["alphaout"] .. "&)") end end line.text = text subs[i] = line end end function talphaconfig(subs, sel) dialog_config= { { class="label", x=0,y=1,width=1,height=1, label="Alpha in:", }, { class="label", x=0,y=2,width=1,height=1, label="Alpha out:", }, { class="checkbox",name="tcheck", x=4,y=0,width=2,height=1, label="Enable times/acceleration", value=true }, { class="dropdown",name="alphain", x=1,y=1,width=2,height=1, items={"00","10","20","30","40","50","60","70","80","90","A0","B0","C0","D0","E0","F0","FF"}, value="00" }, { class="dropdown",name="alphaout", x=1,y=2,width=2,height=1, items={"00","10","20","30","40","50","60","70","80","90","A0","B0","C0","D0","E0","F0","FF"}, value="FF" }, { class="floatedit",name="start", x=5,y=1,width=1,height=1, value="0" }, { class="floatedit",name="end", x=5,y=2,width=1,height=1, value="0" }, { class="floatedit",name="accel", x=5,y=3,width=1,height=1, value="1" }, { class="label", x=0,y=0,width=3,height=1, label="Alpha before and after transform", }, { class="label", x=4,y=1,width=1,height=1, label="\\t start", }, { class="label", x=4,y=2,width=1,height=1, label="\\t end", }, { class="label", x=4,y=3,width=1,height=1, label="accel", }, { class="label", x=0,y=3,width=3,height=1, label="\\in\\t(start,end,accel,\\out)", }, } pressed, results = aegisub.dialog.display(dialog_config,{"Transform","Cancel"}) if pressed=="Cancel" then aegisub.cancel() end

if pressed=="Transform" then talpha(subs, sel) end end function transform_alpha(subs, sel) talphaconfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, transform_alpha) -- Go from \clip(x1,y1,x2,y2) to \clip(x1,y1,x2,y2)\t(\clip(x3,y3,x4,y4)). -- Coordinates are read from the line. You can set by how much x and y should change, and new coordinates will be calculated. -- "use next line's clip" allows you to use clip from the next line. -- Create a line after your current one (or just duplicate), set the clip you want to transform to on it, check "use next line's clip". -- The clip from the next line will be used for the transform, and the line will be deleted. script_name="Transform clip" script_description="Transform clip" script_author="unanimated" script_version="1.3" function transclip(subs,sel,act) line=subs[act] text=line.text if not text:match("\\i?clip%([%d%.%-]+,") then aegisub.dialog.display({{class="label", label="Error: rectangular clip required on active line.",x=0,y=0,width=1,height=2}},{"OK"},{close='OK'}) aegisub.cancel() end ctype,cc1,cc2,cc3,cc4=text:match("(\\i?clip)%(([%d%.%-]+),([%d%.%-]+),([%d%.%-]+),([%d%.%-]+)%)") clipconfig={ {x=0,y=0,width=2,height=1,class="label",label=" \\clip(", }, {x=2,y=0,width=3,height=1,class="edit",name="orclip",value=cc1..","..cc2..","..cc3..","..cc4 }, {x=5,y=0,width=1,height=1,class="label",label=")", }, {x=0,y=1,width=2,height=1,class="label",label="\\t(\\clip(", }, {x=2,y=1,width=3,height=1,class="edit",name="klip",value=cc1..","..cc2..","..cc3..","..cc4 }, {x=5,y=1,width=1,height=1,class="label",label=")", }, {x=0,y=2,width=5,height=1,class="label",label="Move x and y for new coordinates by:", }, {x=0,y=3,width=1,height=1,class="label",label="x:", }, {x=3,y=3,width=1,height=1,class="label",label="y:", }, {x=1,y=3,width=2,height=1,class="floatedit",name="eks"}, {x=4,y=3,width=1,height=1,class="floatedit",name="wai"}, {x=0,y=4,width=5,height=1,class="label",label="Start / end / accel:", }, {x=1,y=5,width=2,height=1,class="edit",name="accel",value="0,0,1," }, {x=4,y=5,width=1,height=1,class="checkbox",name="two",label="use next line's clip",value=false,hint="use clip from the next line (line will be deleted)"}, } buttons={"Transform","Calculate coordinates","Cancel"} pressed,res=aegisub.dialog.display(clipconfig,buttons,{ok='Transform',close='Cancel'}) if pressed=="Cancel" then aegisub.cancel() end

repeat if pressed=="Calculate coordinates" then xx=res.eks yy=res.wai for key,val in ipairs(clipconfig) do if val.name=="klip" then val.value=cc1+xx..","..cc2+yy..","..cc3+xx..","..cc4+yy end if val.name=="accel" then val.value=res.accel end end pressed,res=aegisub.dialog.display(clipconfig,buttons,{ok='Transform',close='Cancel'}) end until pressed~="Calculate coordinates" if pressed=="Transform" then newcoord=res.klip end

if res.two then nextline=subs[act+1] nextext=nextline.text if not nextext:match("\\i?clip%([%d%.%-]+,") then aegisub.dialog.display({{class="label", label="Error: second line must contain a rectangular clip.",x=0,y=0,width=1,height=2}},{"OK"},{close='OK'}) aegisub.cancel() else nextclip=nextext:match("\\i?clip%(([%d%.%-,]+)%)") text=text:gsub("^({\\[^}]*)}","%1\\t("..res.accel..ctype.."("..nextclip..")}") end else text=text:gsub("^({\\[^}]*)}","%1\\t("..res.accel..ctype.."("..newcoord..")}") end text=text:gsub("0,0,1,\\","\\") line.text=text subs[act]=line if res.two then subs.delete(act+1) end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, transclip) -- change font size, \fscx and \fscy for smoother scaling with Mocha script_name = "Add scaling for Mocha" script_description = "Add scaling tags for mocha tracking." script_author = "unanimated" script_version = "1.0" function scale(subs, sel) for z, i in ipairs(sel) do local l = subs[i] l.text=l.text:gsub("\\fs[%d%.]-([\\}])","%1") l.text=l.text:gsub("\\fscx[%d%.]-([\\}])","%1") l.text=l.text:gsub("\\fscy[%d%.]-([\\}])","%1") l.text=l.text:gsub("{}","") l.text="{\\fs" .. results["fs"] .. "\\fscx" .. results["fscx"] .. "\\fscy" .. results["fscy"] .. "}" .. l.text if results["tag"]=="{Start of tag block" then l.text=l.text:gsub("^({\\[^}]-)}{\\","%1\\") else l.text=l.text:gsub("^{(\\[^}]-)}{(\\[^}]-)}","{%2%1}") end subs[i] = l end aegisub.set_undo_point(script_name) return sel end function scaleconfig(subs, sel) dialog_config= { { class="label", x=0,y=0,width=3,height=1, label="Select font size, fscx, and fscy", }, { class="label", x=0,y=1,width=1,height=1, label="\\fs", }, { class="label", x=0,y=2,width=1,height=1, label="\\fscx", }, { class="label", x=0,y=3,width=1,height=1, label="\\fscy", }, { class="dropdown",name="fs", x=1,y=1,width=1,height=1, items={"1","2","3","4","5","6","7","8","9","10"}, value="2" }, { class="edit",name="fscx", x=1,y=2,width=1,height=1, value="2000" }, { class="edit",name="fscy", x=1,y=3,width=1,height=1, value="2000" }, { class="dropdown",name="tag", x=1,y=4,width=1,height=1, items={"{Start of tag block","End of tag block}"}, value="{Start of tag block" }, } pressed, results = aegisub.dialog.display(dialog_config,{"hai","iie"}) if pressed=="iie" then aegisub.cancel() end if pressed=="hai" then scale(subs, sel) end end function mocha_scaling(subs, sel) scaleconfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, mocha_scaling) -- Creates a new layer with more blur and some alpha. The original line's layer is raised by 1, -- so if you want to combine this with Duplicate and Blur, run this one first, otherwise you mess up the layers. -- If you already ran Duplicate and Blur or just need to fix layers for multiple lines, there's another function for that here. -- The two functions are independent - you either run one or the other. script_name = "Add glow / Raise layer" script_description = "Add glow to signs or raise layer for all selected lines" script_author = "unanimated" script_version = "1.0" function glow(subs, sel) for i=#sel,1,-1 do line = subs[sel[i]] text = subs[sel[i]].text if text:match("\\blur") then line2=line line2.layer=line2.layer+1 subs.insert(sel[i]+1,line2) text=text:gsub("(\\blur)[%d%.]*([\\}])","%1"..res["blur"].."\\alpha&H"..res["alfa"].."&%2") line.layer=line.layer-1 line.text = text else aegisub.dialog.display({{class="label", label="What are you doing? Where is your blur?",x=0,y=0,width=1,height=2}},{"OK"}) end subs[sel[i]] = line end end function layeraise(subs, sel) for i=#sel,1,-1 do line = subs[sel[i]] text = subs[sel[i]].text if res["raise"]=="raise by:" then line.layer=line.layer+res["layer"] else if line.layer-res["layer"]>=0 then line.layer=line.layer-res["layer"] else aegisub.dialog.display({{class="label", label="You're dumb. Layers can't go below 0.",x=0,y=0,width=1,height=2}},{"OK"}) end end subs[sel[i]] = line end return sel end function konfig(subs, sel) dialog_config= { { x=0,y=0,width=2,height=1,class="label",label="Add glow to signs", },

{ x=0,y=1,width=1,height=1,class="label",label="Glow blur:" }, { x=0,y=2,width=1,height=1,class="label",label="Glow alpha:" },

{ x=1,y=1,width=1,height=1,class="floatedit",name="blur",value="3" }, { x=1,y=2,width=1,height=1,class="dropdown",name="alfa", items={"20","30","40","50","60","70","80","90","A0","B0","C0","D0"},value="80" },

{ x=3,y=0,width=1,height=1,class="label",label="Layer:", }, { x=3,y=1,width=1,height=1,class="dropdown",name="raise",items={"raise by:","lower by:"},value="raise by:" }, { x=3,y=2,width=1,height=1,class="dropdown",name="layer",items={"1","2","3","4","5"},value="1" },

} buttons={"Apply glow","- cancel -","Change layer"} pressed, res = aegisub.dialog.display(dialog_config,buttons) if pressed=="Apply glow" then glow(subs, sel) end if pressed=="Change layer" then layeraise(subs, sel) end end function addglow(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) end aegisub.register_macro(script_name, script_description, addglow) script_name = "xy BorderShadow" script_description = "Adds xbord / ybord / xshad / yshad to selected lines" script_author = "unanimated" script_version = "1.0" function xybs(subs, sel) for z, i in ipairs(sel) do line = subs[i] text = subs[i].text

if text:match("^{\\")==nil then text="{\\}"..text end -- add {\} if line has no tags

-- \xbord if results["xbord1"]==true then if text:match("^{[^}]*\\xbord%d") then text=text:gsub("^({[^}]*\\xbord)([%d%.]+)","%1"..results["xbord2"]) else text=text:gsub("^({\\[^}]*)}","%1\\xbord"..results["xbord2"].."}") end end -- \ybord if results["ybord1"]==true then if text:match("^{[^}]*\\ybord%d") then text=text:gsub("^({[^}]*\\ybord)([%d%.]+)","%1"..results["ybord2"]) else text=text:gsub("^({\\[^}]*)}","%1\\ybord"..results["ybord2"].."}") end end -- \xshad if results["xshad1"]==true then if text:match("^{[^}]*\\xshad%d") then text=text:gsub("^({[^}]*\\xshad)([%d%.]+)","%1"..results["xshad2"]) else text=text:gsub("^({\\[^}]*)}","%1\\xshad"..results["xshad2"].."}") end end -- \yshad if results["yshad1"]==true then if text:match("^{[^}]*\\yshad%d") then text=text:gsub("^({[^}]*\\yshad)([%d%.]+)","%1"..results["yshad2"]) else text=text:gsub("^({\\[^}]*)}","%1\\yshad"..results["yshad2"].."}") end end

text=text:gsub("\\\\","\\") text=text:gsub("\\}","}") -- clean up \\ line.text = text subs[i] = line end end function konfig(subs, sel) dialog_config= { { x=2,y=0,width=4,height=1,class="label",label="Tags are added at the end of tag string.", },

{ x=2,y=1,width=1,height=1,class="checkbox",name="xbord1",label="\\xbord",value=false }, { x=2,y=2,width=1,height=1,class="checkbox",name="ybord1",label="\\ybord",value=false }, { x=2,y=3,width=1,height=1,class="checkbox",name="xshad1",label="\\xshad",value=false }, { x=2,y=4,width=1,height=1,class="checkbox",name="yshad1",label="\\yshad",value=false },

{ x=3,y=1,width=1,height=1,class="floatedit",name="xbord2",value="",min="0" }, { x=3,y=2,width=1,height=1,class="floatedit",name="ybord2",value="",min="0" }, { x=3,y=3,width=1,height=1,class="floatedit",name="xshad2",value="" }, { x=3,y=4,width=1,height=1,class="floatedit",name="yshad2",value="" },

{ x=2,y=5,width=4,height=1,class="label",label="They will override existng \\bord or \\shad tags.", }, } buttons={"Apply to selected lines","check all","cancel"}

repeat

if pressed=="check all" then for key,val in ipairs(dialog_config) do if val.class=="checkbox" then val.value=true end end end pressed,results=aegisub.dialog.display(dialog_config,buttons) until pressed~="check all"

if pressed=="Apply to selected lines" then xybs(subs, sel) end end function bordershadow(subs, sel) konfig(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, bordershadow) script_name="Duplicate and Shift" script_description="old Aegisub's Ctrl+D function" script_author="unanimated" script_version="1.1" all_at_the_end=true -- if true, all new lines go after the end time of the last line. if false, each new line goes right after its original. function duplishift(subs, sel) last=subs[sel[#sel]] endtime=last.end_time ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame shiftframe=ms2fr(endtime) newsel={} for i=#sel,1,-1 do line=subs[sel[i]] l2=line startfr=ms2fr(l2.start_time) endfr=ms2fr(l2.end_time) if all_at_the_end then if line.end_time==endtime then line.end_time=fr2ms(shiftframe) subs[sel[i]]=line end l2.start_time=fr2ms(shiftframe) l2.end_time=fr2ms(shiftframe+1) else line.start_time=fr2ms(startfr) line.end_time=fr2ms(endfr) subs[sel[i]]=line l2.start_time=fr2ms(endfr) l2.end_time=fr2ms(endfr+1) end subs.insert(sel[#sel]+1,l2) table.insert(newsel,sel[#sel]+i) end return newsel end aegisub.register_macro(script_name, script_description, duplishift) -- Adds blur 0.6, then cycles through 0.8, 1, 1.2, 1.5, 2, 3, 4, 5, 0.4, 0.5, back to 0.6. script_name="Blur Cycle" script_description="Adds blur" script_author="unanimated" script_version="1.7" sequence={"0.6","0.8","1","1.2","1.5","2","3","4","5","6","8","0.4","0.5"} -- you can modify this function blur(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text tf="" if text:match("^{\\[^}]-}") then tags,after=text:match("^({\\[^}]-})(.*)") if tags:match("\\t") then for t in tags:gmatch("\\t%b()") do tf=tf..t end tags=tags:gsub("\\t%b()","") :gsub("{}","") text=tags..after end end

bl=text:match("^{[^}]-\\blur([%d%.]+)") if bl~=nil then for b=1,#sequence do if bl==sequence[b] then bl2=sequence[b+1] end end if bl2==nil then for b=1,#sequence do if tonumber(bl)

text=text:gsub("^({\\[^}]-)}","%1"..tf.."}") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name,script_description,blur) -- Adds \bord0 to selected lines, then cycles through 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, back to 0. script_name="Border cycle" script_description="Add border tags to selected lines." script_author="unanimated" script_version="1.7" sequence={"0","1","2","3","4","5","6","7","8","9","10","11","12"} -- you can modify this function bord(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text tf="" if text:match("^{\\[^}]-}") then tags,after=text:match("^({\\[^}]-})(.*)") if tags:match("\\t") then for t in tags:gmatch("\\t%b()") do tf=tf..t end tags=tags:gsub("\\t%b()","") :gsub("{}","") text=tags..after end end

br=text:match("^{[^}]-\\bord([%d%.]+)") if br~=nil then for b=1,#sequence do if br==sequence[b] then br2=sequence[b+1] end end if br2==nil then for b=1,#sequence do if tonumber(br)

text=text:gsub("^({\\[^}]-)}","%1"..tf.."}") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name,script_description,bord) -- Adds \shad0 to selected lines, then cycles through 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, back to 0. script_name="Shadow cycle" script_description="Add shadow tags to selected lines." script_author="unanimated" script_version="1.7" sequence={"0","1","2","3","4","5","6","7","8","9","10","11","12"} -- you can modify this function shad(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text tf="" if text:match("^{\\[^}]-}") then tags,after=text:match("^({\\[^}]-})(.*)") if tags:match("\\t") then for t in tags:gmatch("\\t%b()") do tf=tf..t end tags=tags:gsub("\\t%b()","") :gsub("{}","") text=tags..after end end

sh=text:match("^{[^}]-\\shad([%d%.]+)") if sh~=nil then for b=1,#sequence do if sh==sequence[b] then sh2=sequence[b+1] end end if sh2==nil then for b=1,#sequence do if tonumber(sh)

text=text:gsub("^({\\[^}]-)}","%1"..tf.."}") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name,script_description,shad) -- if an alpha tag exists, changes it to alphaFF, otherwise creates it. if there's alphaFF, cycles through 00, 30, 60, 80, A0, D0, FF script_name="Alpha cycle" script_description="Add alpha tags to selected lines." script_author="unanimated" script_version="1.7" sequence={"FF","00","10","30","60","80","A0","C0","E0"} -- you can modify this function alpha(subs,sel) for z,i in ipairs(sel) do line=subs[i] text=line.text tf="" if text:match("^{\\[^}]-}") then tags,after=text:match("^({\\[^}]-})(.*)") if tags:match("\\t") then for t in tags:gmatch("\\t%b()") do tf=tf..t end tags=tags:gsub("\\t%b()","") :gsub("{}","") text=tags..after end end

al=text:match("^{[^}]-\\alpha&H(%x%x)&") if al~=nil then for b=1,#sequence do if al==sequence[b] then al2=sequence[b+1] end end if al2==nil then for b=2,#sequence do if tonumber(al,16)

text=text:gsub("^({\\[^}]-)}","%1"..tf.."}") line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name,script_description,alpha) -- If you have several signs on the same screen, and the whole screen is moving, -- and you need to apply the same amount of \move to all signs, this is what you use. -- Adjust \move on the first line, select lines so that the one with \move is the first, and the rest have \pos tags. -- The script will change \pos tags to \move and calculate the coordinates from the first line. script_name = "Multimove" script_description = "Apply movement from one line to other lines with position tags" script_author = "unanimated" script_version = "1.1" function move(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text -- error if first line's missing \move tag if x==1 and text:match("\\move")==nil then aegisub.dialog.display({{class="label", label="Missing \\move tag on line 1",x=0,y=0,width=1,height=2}},{"OK"}) mc=1 else -- get coordinates from \move on line 1 if text:match("\\move") then x1,y1,x2,y2,t,m1,m2=nil if text:match("\\move%([%d%.%-]+%,[%d%.%-]+%,[%d%.%-]+%,[%d%.%-]+%,[%d%.%,%-]+%)") then x1,y1,x2,y2,t=text:match("\\move%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%- ]+)%,([%d%.%,%-]+)%)") else x1,y1,x2,y2=text:match("\\move%(([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%,([%d%.%-]+)%)") end m1=x2-x1 m2=y2-y1 -- difference between start/end position end

-- error if any of lines 2+ don't have \pos tag if x~=1 and text:match("\\pos")==nil then poscheck=1 else -- apply move coordinates to lines 2+ if x~=1 and m2~=nil then p1,p2=text:match("\\pos%(([%d%.]+)%,([%d%.]+)%)") if t~=nil then text=text:gsub("\\pos%(([%d%.]+)%,([%d%.]+)%)","\\move%(%1,%2,"..p1+m1..","..p2+m2..","..t.."%)") else text=text:gsub("\\pos%(([%d%.]+)%,([%d%.]+)%)","\\move(%1,%2,"..p1+m1..","..p2+m2..")") end end end

end line.text = text subs[i] = line end if poscheck==1 then aegisub.dialog.display({{class="label", label="Some lines are missing \\pos tags",x=0,y=0,width=1,height=2}},{"OK"}) end x1,y1,x2,y2,t,m1,m2=nil poscheck=0 end function multimove(subs, sel) move(subs, sel) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, multimove) -- creates a 3D effect using layers of shadow (unfortunatelly, vsfilter fucks this up pretty bad with rotations) script_name="Shadow 3D Effect" script_description="Creates 3D Effect from Shadow" script_author="unanimated" script_version="1.0" function threedee(subs, sel) for i=#sel,1,-1 do local line=subs[sel[i]] local text=subs[sel[i]].text local layer=line.layer

xshad=text:match("^{[^}]-\\xshad([%d%.%-]+)") if xshad==nil then xshad=0 end ax=math.abs(xshad) yshad=text:match("^{[^}]-\\yshad([%d%.%-]+)") if yshad==nil then yshad=0 end ay=math.abs(yshad) if ax>ay then lay=math.floor(ax) else lay=math.floor(ay) end

text2=text:gsub("^({\\[^}]-)}","%1\\3a&HFF&}") :gsub("\\3a&H%x%x&([^}]-)(\\3a&H%x%x&)","%1%2")

for l=lay,1,-1 do line2=line f=l/lay txt=text2 if l==1 then txt=text end line2.text=txt :gsub("\\xshad([%d%.%-]+)",function(a) xx=tostring(f*a) xx=xx:gsub("([%d%-]+%.%d%d)%d+","%1") return "\\xshad"..xx end) :gsub("\\yshad([%d%.%-]+)",function(a) yy=tostring(f*a) yy=yy:gsub("([%d%-]+%.%d%d)%d+","%1") return "\\yshad"..yy end) line2.layer=layer+(lay-l) subs.insert(sel[i]+1,line2) end

if not xshad==0 and not yshad==0 then subs.delete(sel[i]) end end end function shad3d(subs, sel, act) threedee(subs, sel, act) aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, shad3d) script_name = "Split by Linebreaks" script_description = "Split by Linebreaks" -- make a new line for each \N, keep initial tags for every line script_author = "unanimated" script_version = "1.5" function splitbreak(subs, sel) -- 1.5 for i=#sel,1,-1 do line=subs[sel[i]] text=subs[sel[i]].text text=text:gsub("({[^}]-})",function (a) return debreak(a) end)

if not text:match("\\N") then pressed=aegisub.dialog.display({{class="label", label="Selection line "..i.." has no \\N. \nYou can split by spaces or by tags.",x=0,y=0,width=1,height=2}},{"Spaces","Tags","Skip","Cancel"}) if pressed=="Cancel" then aegisub.cancel() end if pressed=="Tags" then text=text:gsub("(.)({\\[^}]-})","%1\\N%2") end

-- split by spaces if pressed=="Spaces" then line2=line text=text:gsub("({[^}]-})",function (a) return despace(a) end) if text:match("^{\\") then -- lines 2+, with initial tags tags=text:match("^({\\[^}]*})") -- initial tags if tags==nil then tags="" end count=0 text=text:gsub("^({\\[^}]*})","%1 ") -- add space for aftern in text:gmatch("%s+([^%s]+)") do -- part after \N [*] aftern=aftern:gsub("_sp_"," ") tags=tags:gsub("_sp_"," ") aftern=aftern:gsub("_break_","\\N") count=count+1 line2.text=tags..aftern -- every new line=initial tags + part after one \N --line2.effect=count line2.text=line2.text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") line2.text=duplikill(line2.text) tags=line2.text:match("^({\\[^}]*})") subs.insert(sel[i]+count,line2) -- insert each match one line further end else -- lines 2+, without initial tags count=0 text=" "..text for aftern in text:gmatch("%s+([^%s]+)") do if tags==nil then tags="" end aftern=aftern:gsub("_sp_"," ") aftern=aftern:gsub("_break_","\\N") count=count+1 line2.text=tags..aftern line2.text=line2.text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") line2.text=duplikill(line2.text) tags=line2.text:match("^({\\[^}]*})") --line2.effect=count subs.insert(sel[i]+count,line2) end end subs.delete(sel[i]) end end

-- split by \N if text:match("\\N")then line2=line if text:match("%*")then text=text:gsub("%*","_asterisk_") end if text:match("^{\\") then -- lines 2+, with initial tags tags=text:match("^({\\[^}]*})") -- initial tags tags2="" count=0 text=text:gsub("\\N","*") -- switch \N for * for aftern in text:gmatch("%*%s*([^%*]*)") do -- part after \N [*] aftern=aftern:gsub("_break_","\\N") :gsub("%s*$","") :gsub("_asterisk_","*") if aftern~="" then count=count+1 line2.text=tags..aftern -- every new line=initial tags + part after one \N --line2.effect=count+1 line2.text=line2.text:gsub("{(\\[^}]-)}{(\\[^}]-)}","{%1%2}") line2.text=duplikill(line2.text) tags=line2.text:match("^({\\[^}]*})") subs.insert(sel[i]+count,line2) -- insert each match one line further end end else -- lines 2+, without initial tags count=0 text=text:gsub("\\N","*") for aftern in text:gmatch("%*%s*([^%*]*)") do aftern=aftern:gsub("_break_","\\N") :gsub("%s*$","") :gsub("_asterisk_","*") if aftern~="" then count=count+1 line2.text=aftern subs.insert(sel[i]+count,line2) end end end if text:match("^{\\") then -- line 1, with initial tags text=text:gsub("^({\\[^}]-})(.-)%*(.*)","%1%2") text=text:gsub("_break_","\\N") :gsub("%s*$","") --line.effect=1 else -- line 1, without initial tags text=text:gsub("^(.-)%*(.*)","%1") text=text:gsub("_break_","\\N") :gsub("%s*$","") end text=text:gsub("_asterisk_","*")

line.text=text subs[sel[i]]=line end end aegisub.set_undo_point(script_name) end function despace(txt) txt=txt:gsub("%s","_sp_") return txt end function debreak(txt) txt=txt:gsub("\\N","_break_") return txt end function duplikill(text) tags1={"blur","be","bord","shad","fs","fsp","fscx","fscy","frz","frx","fry","fax","fay"} for i=1,#tags1 do tag=tags1[i] text=text:gsub("\\"..tag.."[%d%.%-]+([^}]-)(\\"..tag.."[%d%.%-]+)","%1%2") end text=text:gsub("\\1c&","\\c&") tags2={"c","2c","3c","4c","1a","2a","3a","4a","alpha"} for i=1,#tags2 do tag=tags2[i] text=text:gsub("\\"..tag.."&H%x+&([^}]-)(\\"..tag.."&H%x+&)","%1%2") end return text end aegisub.register_macro(script_name, script_description, splitbreak) -- Replaces strikeout or underline tags with \alpha&H00& or \alpha&HFF&. Also @. -- @ -> {\alpha&HFF&} -- @0 -> {\alpha&H00&} -- {\u1} -> {\alpha&HFF&} -- {\u0} -> {\alpha&H00&} -- {\s0} -> {\alpha&HFF&} -- {\s1} -> {\alpha&H00&} -- @E3@ -> {\alpha&HE3&} script_name = "StrikeAlpha" script_description = "StrikeAlpha" script_author = "unanimated" script_version = "1.1" function strikealpha(subs, sel) for x, i in ipairs(sel) do local l=subs[i] l.text=l.text :gsub("\\s1","\\alpha&H00&") :gsub("\\s0","\\alpha&HFF&") :gsub("\\u1","\\alpha&HFF&") :gsub("\\u0","\\alpha&H00&") :gsub("@(%x%x)@","{\\alpha&H%1&}") :gsub("@0","{\\alpha&H00&}") :gsub("@","{\\alpha&HFF&}") subs[i]=l end end aegisub.register_macro(script_name, script_description, strikealpha) -- Copies tags from first selected line to the rest of selected lines. script_name = "Copy Tags" script_description = "Copy tags from first line to others" script_author = "unanimated" script_version = "1.0" function copytags(subs, sel) for x, i in ipairs(sel) do local line = subs[i] local text = subs[i].text if x==1 then tags=text:match("^({\\[^}]*})") end if x~=1 then if text:match("^({\\[^}]*})") then text=text:gsub("^{\\[^}]*}",tags) else text=tags..text end end line.text = text subs[i] = line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, copytags) -- Draw a vectorial clip with the inbuild tool. Run this script to convert it to a vector drawing. -- If you had \pos, the drawing _should_ now be in the exact place the clip was. It also keeps the tags in the first block. script_name = "Convert clip to drawing" script_description = "Converts clip to drawing" script_author = "unanimated" script_version = "1.2" function convertclip(subs, sel) for z, i in ipairs(sel) do local l = subs[i] text=l.text if not text:match("\\clip") then aegisub.cancel() end text=text:gsub("^({\\[^}]-}).*","%1") text=text:gsub("^({[^}]*)\\clip%(m(.-)%)([^}]*)}","%1%3\\p1}m%2") if text:match("\\pos") then local xx,yy=text:match("\\pos%(([%d%.%-]+),([%d%.%-]+)%)") xx=round(xx) yy=round(yy) ctext=text:match("}m ([%d%a%s%-]+)") ctext2=ctext:gsub("([%d%-]+)%s([%d%-]+)",function(a,b) return a-xx.." "..b-yy end) ctext=ctext:gsub("%-","%%-") text=text:gsub(ctext,ctext2) end if not text:match("\\pos") then text=text:gsub("^{","{\\pos(0,0)") end if text:match("\\an") then text=text:gsub("\\an%d","\\an7") else text=text:gsub("^{","{\\an7") end if text:match("\\fscx") then text=text:gsub("\\fscx[%d%.]+","\\fscx100") else text=text:gsub("\\p1","\\fscx100\\p1") end if text:match("\\fscy") then text=text:gsub("\\fscy[%d%.]+","\\fscy100") else text=text:gsub("\\p1","\\fscy100\\p1") end l.text=text subs[i] = l end aegisub.set_undo_point(script_name) end function round(num) if num-math.floor(num)>=0.5 then num=math.ceil(num) else num=math.floor(num) end return num end aegisub.register_macro(script_name, script_description, convertclip) script_name = "Clip to iclip" script_description = "Converts clip to iclip and vice versa" script_author = "unanimated" script_version = "one" function clipiclip(subs, sel) for i=#sel,1,-1 do local line=subs[sel[i]] local text=subs[sel[i]].text if text:match("\\clip") then text=text:gsub("\\clip","\\iclip") elseif text:match("\\iclip") then text=text:gsub("\\iclip","\\clip") end line.text=text subs[sel[i]]=line end end aegisub.register_macro(script_name, script_description, clipiclip) script_name = "Match fscy to fscx" script_description = "Matches fscy to fscx" script_author = "unanimated" script_version = "one" function fscxfscy(subs, sel) for i=#sel,1,-1 do local line=subs[sel[i]] local text=subs[sel[i]].text if text:match("\\fscx") and text:match("\\fscy") then scalx=text:match("\\fscx([%d%.]+)") text=text:gsub("\\fscy[%d%.]+","\\fscy"..scalx) end line.text=text subs[sel[i]]=line end end aegisub.register_macro(script_name, script_description, fscxfscy) -- adds \an8 tag. if there is one, it changes to \an5, then cycles through 1, 2, 3, 4, 6, 7, 9 script_name = "Add an8" script_description = "Adds an8 tags" script_author = "unanimated" script_version = "1.2" function add_an8(subs, sel, act) for z, i in ipairs(sel) do local line = subs[i] local text = subs[i].text if line.text:match("\\an8") then text = text:gsub("\\an8","\\an5") elseif line.text:match("\\an5") then text = text:gsub("\\an5","\\an1") elseif line.text:match("\\an1") then text = text:gsub("\\an1","\\an2") elseif line.text:match("\\an2") then text = text:gsub("\\an2","\\an3") elseif line.text:match("\\an3") then text = text:gsub("\\an3","\\an4") elseif line.text:match("\\an4") then text = text:gsub("\\an4","\\an6") elseif line.text:match("\\an6") then text = text:gsub("\\an6","\\an7") elseif line.text:match("\\an7") then text = text:gsub("\\an7","\\an9") elseif line.text:match("\\an9") then text = text:gsub("\\an9","") text = text:gsub("{}","") else text = "{\\an8}" .. text text = text:gsub("{\\an8}{\\","{\\an8\\") end line.text = text subs[i] = line end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, add_an8) script_name = "Add q2" -- adds/removes a \q2 tag script_description = "adds q2" script_author = "unanimated" script_version = "1.2" function add_q2(subs, sel, active_line) for z, i in ipairs(sel) do local l = subs[i] if l.text:match("\\q2") then l.text = l.text:gsub("\\q2","") l.text = l.text:gsub("{}","") else l.text = "{\\q2}" .. l.text l.text = l.text:gsub("{\\q2}{\\","{\\q2\\") end subs[i] = l end aegisub.set_undo_point(script_name) return sel end aegisub.register_macro(script_name, script_description, add_q2) script_name="Honorificslaughterhouse" script_description="Slaughters Honorifics" script_author="unknown" script_version="six" function honorifix(subs, sel) for i=#subs,1,-1 do if subs[i].class=="dialogue" then local line=subs[i] local text=subs[i].text text=text :gsub("%-san","{-san}") :gsub("%-chan","{-chan}") :gsub("%-kun","{-kun}") :gsub("%-sama","{-sama}") :gsub("%-niisan","{-niisan}") :gsub("%-oniisan","{-oniisan}") :gsub("%-oniichan","{-oniichan}") :gsub("%-oneesan","{-oneesan}") :gsub("%-oneechan","{-oneechan}") :gsub("%-neesama","{-neesama}") :gsub("%-sensei","{-sensei}") :gsub("%-se[mn]pai","{-senpai}") :gsub("%-dono","{-dono}") :gsub("Onii{%-chan}","Brother{Onii-chan}") :gsub("Onii{%-san}","Brother{Onii-san}") :gsub("Onee{%-chan}","Sister{Onee-chan}") :gsub("Onee{%-san}","Sister{Onee-san}") :gsub("Onee{%-sama}","Sister{Onee-sama}") :gsub("onii{%-chan}","brother{onii-chan}") :gsub("onii{%-san}","brother{onii-san}") :gsub("onee{%-chan}","sister{onee-chan}") :gsub("onee{%-san}","sister{onee-san}") :gsub("onee{%-sama}","sister{onee-sama}") line.text=text subs[i]=line end end end aegisub.register_macro(script_name, script_description, honorifix) script_name="Style Switch" script_description="Style Switch" script_author="unanimated" script_version="1.0" function sswitch(subs,sel) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i]) end if subs[i].class=="dialogue" then break end end for x, i in ipairs(sel) do line=subs[i] style=line.style for a,st in ipairs(styles) do if st.name==style then if styles[a+1] then newstyle=styles[a+1].name else newstyle=styles[1].name end style=newstyle break end end if style==line.style then style=styles[1].name end line.style=style subs[i]=line end end aegisub.register_macro(script_name,script_description,sswitch) -- Opens a menu with styles for a line with an empty \\r tag and sets the selected name. script_name="Reset Style" script_description="Dropdown menu for choosing a style after \\r" script_author="unanimated" script_version="1.0" function reset(subs,sel,act) styles={} for i=1,#subs do if subs[i].class=="style" then table.insert(styles,subs[i].name) end if subs[i].class=="dialogue" then break end end line=subs[act] text=line.text if text:match("\\r[\\}]") then dial={{x=0,y=0,width=8,height=1,class="dropdown",name="st",items=styles,value=styles[1]}} pressed,res=aegisub.dialog.display(dial,{"Yes","No"},{ok='Yes',close='No'}) if pressed=="No" then aegisub.cancel() end text=text:gsub("\\r([\\}])","\\r"..res.st.."%1") end line.text=text subs[act]=line aegisub.set_undo_point(script_name) end aegisub.register_macro(script_name, script_description, reset) -- This will change \r[style] to tags with the [style]'s properties. Works with multiple \r's in the line. script_name="Reset-to-tags" script_description="Replaces \\r[style] with the [style]'s actual properties" script_author="unanimated" script_version="1.0" keep_r=false -- this will keep non-style properties like rotations for the whole line. change to 'true' to have the real \r effect. function r2t(subs, sel) for x, i in ipairs(sel) do line=subs[i] text=line.text style1=line.style text=text:gsub("\\r([\\}])","\\r"..style1.."%1") for R in text:gmatch("\\r([^\\}]+)") do style2=R sr1=stylechk(subs,style1) sr2=stylechk(subs,style2) if keep_r then newstyle="\\r" else newstyle="" end

border="\\bord"..sr2.outline shadow="\\shad"..sr2.shadow size="\\fs"..sr2.fontsize space="\\fsp"..sr2.spacing scalex="\\fscx"..sr2.scale_x scaley="\\fscy"..sr2.scale_y name="\\fn"..sr2.fontname if sr2.bold then bold="\\b1" else bold="\\b0" end if sr2.italic then italic="\\i1" else italic="\\i0" end c1="\\c"..sr2.color1:gsub("H%x%x","H") c2="\\2c"..sr2.color2:gsub("H%x%x","H") c3="\\3c"..sr2.color3:gsub("H%x%x","H") c4="\\4c"..sr2.color4:gsub("H%x%x","H") a1="\\1a&"..sr2.color1:match("H%x%x").."&" a2="\\2a&"..sr2.color2:match("H%x%x").."&" a3="\\3a&"..sr2.color3:match("H%x%x").."&" a4="\\4a&"..sr2.color4:match("H%x%x").."&"

if sr1.outline~=sr2.outline then add(border) end if sr1.shadow~=sr2.shadow then add(shadow) end if sr1.fontname~=sr2.fontname then add(name) end if sr1.fontsize~=sr2.fontsize then add(size) end if sr1.spacing~=sr2.spacing then add(space) end if sr1.scale_x~=sr2.scale_x then add(scalex) end if sr1.scale_y~=sr2.scale_y then add(scaley) end if sr1.color1:gsub("H%x%x","H")~=sr2.color1:gsub("H%x%x","H") then add(c1) end if sr1.color2:gsub("H%x%x","H")~=sr2.color2:gsub("H%x%x","H") then add(c2) end if sr1.color3:gsub("H%x%x","H")~=sr2.color3:gsub("H%x%x","H") then add(c3) end if sr1.color4:gsub("H%x%x","H")~=sr2.color4:gsub("H%x%x","H") then add(c4) end if sr1.color1:match("H%x%x")~=sr2.color1:match("H%x%x") then add(a1) end if sr1.color2:match("H%x%x")~=sr2.color2:match("H%x%x") then add(a2) end if sr1.color3:match("H%x%x")~=sr2.color3:match("H%x%x") then add(a3) end if sr1.color4:match("H%x%x")~=sr2.color4:match("H%x%x") then add(a4) end if sr1.bold~=sr2.bold then add(bold) end if sr1.italic~=sr2.italic then add(italic) end

text=text:gsub("\\r"..style2,newstyle) style1=style2 end

line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end function add(x) newstyle=newstyle..x end function stylechk(subs,stylename) for i=1, #subs do if subs[i].class=="style" then local st=subs[i] if stylename==st.name then styleref=st break end end end return styleref end aegisub.register_macro(script_name, script_description, r2t) script_name="Rotated Shadow" script_description="Rotates shadow along with frz" script_author="unanimated" script_version="1.0" function shadowrot(subs, sel) for z, i in ipairs(sel) do local line=subs[i] local text=subs[i].text if text:match("\\frz") then stylechk(subs,line.style) lshad=text:match("^{[^}]-\\shad([%d%.]+)") shad=tonumber(lshad) if shad==nil then shad=styleref.shadow end rot=tonumber(text:match("\\frz([%d%.%-]+)")) if rot<0 then rot=rot+360 end if rot==0 then x=1 y=1 end if rot>0 then x=(rot/45)*0.3+1 y=1-(rot/45) end if rot>45 then x=((90-rot)/45)*0.3+1 y=1-(rot/45) end if rot>90 then x=(135/rot)*2-2 y=((90-rot)/45)*0.3-1 end if rot>135 then x=(180/rot)*3-4 y=((180-rot)/45)*-0.3-1 end if rot>180 then x=((180-rot)/45)*0.3-1 y=(rot/45)-5 end if rot>225 then x=((270-rot)/45)*-0.3-1 y=(rot/45)-5 end if rot>270 then x=0-((315-rot)/45) y=((rot-270)/45)*0.3+1 end if rot>315 then x=1-((360-rot)/45) y=((360-rot)/45)*0.3+1 end

x=tostring(x) x=x:gsub("(%d%.%d%d)%d+","%1") y=tostring(y) y=y:gsub("(%d%.%d%d)%d+","%1")

if lshad==nil then text=text:gsub("^({\\[^}]-)}","%1\\xshad"..x*shad.."\\yshad"..y*shad.."}") end text=text:gsub("(\\shad)([%d%.]+)",function(a,b) return "\\xshad"..x*b.."\\yshad"..y*b end)

end line.text=text subs[i]=line end aegisub.set_undo_point(script_name) return sel end function stylechk(subs,stylename) for i=1, #subs do if subs[i].class=="style" then local st=subs[i] if stylename==st.name then styleref=st end end end return styleref end aegisub.register_macro(script_name, script_description, shadowrot) script_name = "ShadowyBorderx" script_description = "Convert bord to xbord+ybord and shad to xshad+yshad." script_author = "unanimated" script_version = "1.1" -- reads also from style include("karaskel.lua") function shadbord(subs, sel) local meta,styles=karaskel.collect_head(subs,false) for x, i in ipairs(sel) do local l=subs[i] karaskel.preproc_line(subs,meta,styles,l) border=l.styleref.outline shadow=l.styleref.shadow l.text=l.text:gsub("\\bord([%d%.]+)","\\xbord%1\\ybord%1") l.text=l.text:gsub("\\shad([%d%.%-]+)","\\xshad%1\\yshad%1") if not l.text:match("\\[xy]?shad") then l.text="{\\xshad"..shadow.."\\yshad"..shadow.."}"..l.text l.text=l.text:gsub("({\\xshad[%d%.]+\\yshad[%d%.]+)}{\\","%1\\") end if not l.text:match("\\[xy]?bord") then l.text="{\\xbord"..border.."\\ybord"..border.."}"..l.text l.text=l.text:gsub("({\\xbord[%d%.]+\\ybord[%d%.]+)}{\\","%1\\") end subs[i]=l end end aegisub.register_macro(script_name, script_description, shadbord) Script Properties

version | save help in adds modifies removes modifies modifies handles inline line repeat modifies Name GUI unicode | macros config GUI tags tags tags times text trnsfrms tags breaks last linecount Cycles v1.8 | 5 iBus v1.7 | 4 Line Breaker v2.3 | 3 settings \N Join / Split / Snap v1.2 | 3 Jump to Next v2.0 |13 Aladin's Lamp v1.0 | 3 order order Script Cleanup v3.4 | 1 overlaps spaces remove Blur and Glow v2.5 | 1 layers HYDRA v5.0 | 1 special f. special f. special f. Hyperdimensional v4.0 | 1 fbf \N only Relocator Recalculator v3.0 | 1 Colorize v4.5 | 1 Selectricks v2.8 | 1 Change Case v3.0 | 1 Multi-Line Editor v1.7 | 1 MultiCopy v3.4 | 1 Apply Fade v3.9 | 1 Significance v3.0 | 1 ShiftCut v2.8 | 1 Time Signs v2.8 | 1 blur Masquerade v2.5 | 1 masks NecrosCopy v3.0 | 1 Encode - Hardsub v1.2 | 1 Multiplexer v1.1 | 1

Colours: NO, Marginally, YES, Main Function / The Whole Point, Not Applicable/Relevant. handles trnsfrms - is written to handle transforms correctly inline tags - is written to handle inline tags correctly line breaks - is written to handle line breaks correctly unicode - supports non-standard characters (øåæ) in letter-by-letter operations modifies linecount - adds or removes lines luanimated evolution

Things weren't always as cool as they are now...

Recalculator

1.0 (August 2013) 1.5 (February 2014) 1.8 (June 2014)

2.0 (June 2014) 2.4 (April 2015)

3.0 (May 2015)

Colorize

1.0 (August 2013) 1.5 (October 2013) 2.0 (December 2013) 3.2 (February 2014) 3.6 (July 2014)

3.8 (July 2014) 4.5 (May 2015)

Apply Fade

1.0 (July 2013) 2.0 (December 2013) 3.9 (April 2015) Hydra

1.5 (July 2013)

5.0 (May 2015)

Hyperdimensional Relocator 1.0 (September 2013)

4.0 (May 2015)

Blur and Glow

1.0 (June 2013)

2.4 (September 2014)