export script_name = "Resample Perspective" export script_description = "Apply after resampling a script in Aegisub to fix any lines with 3D rotations." export script_author = "arch1t3cht" export script_namespace = "arch.Resample" export script_version = "1.3.4" DependencyControl = require "l0.DependencyControl" dep = DependencyControl{ feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json", { {"a-mo.LineCollection", version: "1.3.0", url: "https://github.com/TypesettingTools/Aegisub-Motion", feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json"}, {"l0.ASSFoundation", version: "0.5.0", url: "https://github.com/TypesettingTools/ASSFoundation", feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"}, {"arch.Math", version: "0.1.8", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"}, {"arch.Perspective", version: "0.2.4", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"}, } } LineCollection, ASS, AMath, APersp = dep\requireModules! {:Matrix} = AMath {:transformPoints, :tagsFromQuad} = APersp logger = dep\getLogger! alltags = {"shear_x", "shear_y", "scale_x", "scale_y", "angle", "angle_x", "angle_y", "origin", "position", "outline", "outline_x", "outline_y", "shadow", "shadow_x", "shadow_y"} usedtags = {"shear_x", "shear_y", "scale_x", "scale_y", "angle", "angle_x", "angle_y", "origin", "position", "outline_x", "outline_y", "shadow_x", "shadow_y"} resample = (ratiox, ratioy, centerorg, subs, sel) -> anamorphic = math.max(ratiox, ratioy) / math.min(ratiox, ratioy) > 1.01 lines = LineCollection subs, sel, () -> true lines\runCallback (lines, line) -> data = ASS\parse line -- No perspective tags, we don't need to do anything return if not anamorphic and #data\getTags({"angle_x", "angle_y"}) == 0 tagvals = data\getEffectiveTags(-1, true, true, true).tags return if not anamorphic and tagvals.angle_x.value == 0 and tagvals.angle_y.value == 0 width, height = 0, 0 has_text, has_drawing = false, false data\callback (section) -> if section.class == ASS.Section.Text has_text = true width, height = data\getTextExtents! if section.class == ASS.Section.Drawing has_drawing = true bounds = section\getBounds! width, height = bounds.w, bounds.h if has_text and has_drawing aegisub.log("Line #{line.humanizedNumber} has both text and drawings! Skipping.\n") return if has_text and (width == 0 or height == 0) aegisub.log("Warning: Line #{line.humanizedNumber} has zero width or height!\n") -- Width and height can be 0 for drawings width = math.max(width, 0.01) height = math.max(height, 0.01) width /= (tagvals.scale_x.value / 100) height /= (tagvals.scale_y.value / 100) if data\getPosition().class == ASS.Tag.Move aegisub.log("Line #{line.humanizedNumber} has \\move! Skipping.\n") return -- Do some checks for cases that break this script -- These are a bit more aggressive than necessary (e.g. two tags of the same type in the same section will trigger this detection but not break resampling) -- but I can't be bothered to be more exact. Users can run ASSWipe before resampling or something. for tname in *alltags if #data\getTags({tname}) >= 2 aegisub.log("Warning: Line #{line.humanizedNumber} has more than one #{ASS.tagMap[tname].overrideName} tag! This might break resampling.") -- Assf doesn't support nested transforms so this code could be much simpler, but a) I only found that out after writing this and b) I guess I can -- keep this code around in case it ever starts supporting them checkTransformTags = (section, initial) -> if not initial for tname in *alltags if #section\getTags({tname}) >= 1 aegisub.log("Warning: Line #{line.humanizedNumber} contains a #{ASS.tagMap[tname].overrideName} tag in a transform tag! This might break resampling.") section\modTags {"transform"}, (tag) -> checkTransformTags tag.tags, false tag checkTransformTags data, true -- Manually enforce the relations between tags if #data\getTags({"origin"}) == 0 tagvals.origin.x = tagvals.position.x tagvals.origin.y = tagvals.position.y for name in *{"outline", "shadow"} for coord in *{"x", "y"} cname = "#{name}_#{coord}" if #data\getTags({cname}) == 0 tagvals[cname].value = tagvals[name].value -- Set up the tags data\removeTags alltags data\insertTags [ tagvals[k] for k in *usedtags ] -- Revert Aegisub's resampling. for tag in *{"position", "origin"} tagvals[tag].x *= ratiox tagvals[tag].y *= ratioy tagvals.scale_x.value *= (ratiox / ratioy) -- Aspect ratio resampling -- Store the previous \fscx\fscy oldscale = { k,tagvals[k].value for k in *{"scale_x", "scale_y"} } -- Get the original rendered quad -- Note that we use ratioy in both dimensions here, since font sizes in .ass rendering -- only scale with the height. quad = transformPoints(tagvals, ratioy * width, ratioy * height) -- Transform it back to the new coordinates tagvals.origin.x /= ratiox tagvals.origin.y /= ratioy quad *= Matrix.diag(1 / ratiox, 1 / ratioy) tagsFromQuad(tagvals, quad, width, height, centerorg) -- Correct \bord and \shad for the \fscx\fscy change for name in *{"outline", "shadow"} for coord in *{"x", "y"} tagvals["#{name}_#{coord}"].value *= tagvals["scale_#{coord}"].value / oldscale["scale_#{coord}"] -- Rejoice data\cleanTags 4 data\commit! lines\replaceLines! resample_ui = (subs, sel) -> video_width, video_height = aegisub.video_size! button, results = aegisub.dialog.display({{ class: "label", label: "Source Resolution: ", x: 0, y: 0, width: 1, height: 1, }, { class: "intedit", name: "srcresx", value: 1280, x: 1, y: 0, width: 1, height: 1, }, { class: "label", label: "x", x: 2, y: 0, width: 1, height: 1, }, { class: "intedit", name: "srcresy", value: 720, x: 3, y: 0, width: 1, height: 1, }, { class: "label", label: "Target Resolution: ", x: 0, y: 1, width: 1, height: 1, }, { class: "intedit", name: "targetresx", value: video_width or 1920, x: 1, y: 1, width: 1, height: 1, }, { class: "label", label: "x", x: 2, y: 1, width: 1, height: 1, }, { class: "intedit", name: "targetresy", value: video_height or 1080, x: 3, y: 1, width: 1, height: 1, }, { class: "checkbox", label: "Force center \\org", hint: "If on, all lines will use the center of their quad as the \\org point. If off, the \\org of the lines will be preserved. This option should not change rendering except for rounding errors." name: "centerorg" x: 0, y: 2, width: 2, height: 1, }}) resample(results.srcresx / results.targetresx, results.srcresy / results.targetresy, results.centerorg, subs, sel) if button dep\registerMacro resample_ui