diff --git a/DependencyControl.json b/DependencyControl.json index 8ae4fd3..eee4344 100644 --- a/DependencyControl.json +++ b/DependencyControl.json @@ -200,6 +200,12 @@ "name": "Perspective", "url": "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", "version": "1.1.0" + }, + { + "moduleName": "arch.Util", + "name": "Util", + "url": "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", + "version": "0.1.0" } ] } @@ -219,6 +225,9 @@ ], "0.1.4": [ "Various fixes and improvements to the line2fbf logic (more robust transform handling, fixing transforms of colors and rect clips, fixing fades for lines without alpha tags)" + ], + "0.1.5": [ + "Move line2fbf function to a separate Util module" ] } }, @@ -639,6 +648,48 @@ "Export an_xshift and an_yshift" ] } + }, + "arch.Util": { + "fileBaseUrl": "@{fileBaseUrl}/modules/@{namespacePath}", + "url": "@{baseUrl}#@{namespace}", + "author": "arch1t3cht", + "name": "Util", + "description": "Utility functions used in some of my scripts", + "channels": { + "release": { + "version": "0.1.0", + "released": "2024-01-22", + "default": true, + "files": [ + { + "name": ".moon", + "url": "@{fileBaseUrl}@{fileName}", + "sha1": "" + } + ], + "requiredModules": [ + { + "moduleName": "a-mo.Line", + "version": "1.5.3", + "name": "Aegisub-Motion (Line)", + "url": "https://github.com/TypesettingTools/Aegisub-Motion", + "feed": "@{feed:a-mo}" + }, + { + "moduleName": "l0.ASSFoundation", + "name": "ASSFoundation", + "url": "https://github.com/TypesettingTools/ASSFoundation", + "version": "0.5.0", + "feed": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json" + } + ] + } + }, + "changelog": { + "0.1.0": [ + "Initial Release with line2fbf function" + ] + } } } } diff --git a/macros/arch.PerspectiveMotion.moon b/macros/arch.PerspectiveMotion.moon index 9d4b10e..d92bb0b 100644 --- a/macros/arch.PerspectiveMotion.moon +++ b/macros/arch.PerspectiveMotion.moon @@ -2,14 +2,12 @@ export script_name = "Aegisub Perspective-Motion" export script_description = "Apply perspective motion tracking data" export script_author = "arch1t3cht" export script_namespace = "arch.PerspectiveMotion" -export script_version = "0.1.4" +export script_version = "0.1.5" DependencyControl = require "l0.DependencyControl" dep = DependencyControl{ feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json", { - {"a-mo.Line", version: "1.5.3", url: "https://github.com/TypesettingTools/Aegisub-Motion", - feed: "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/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", @@ -18,10 +16,12 @@ dep = DependencyControl{ feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"}, {"arch.Perspective", version: "1.1.0", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"}, + {"arch.Util", version: "0.1.0", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", + feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"}, "aegisub.clipboard", } } -Line, LineCollection, ASS, AMath, APersp, clipboard = dep\requireModules! +LineCollection, ASS, AMath, APersp, Util, clipboard = dep\requireModules! {:Point, :Matrix} = AMath {:Quad, :an_xshift, :an_yshift, :relevantTags, :usedTags, :transformPoints, :tagsFromQuad, :prepareForPerspective} = APersp @@ -31,183 +31,6 @@ die = (errmsg) -> aegisub.log(errmsg .. "\n") aegisub.cancel! --- rounds a ms timestamp to cs just like Aegisub does -round_to_cs = (time) -> - (time + 5) - (time + 5) % 10 - --- gets the exact starting timestamp of a given frame, --- unlike aegisub.frame_from_ms, which returns a timestamp in the --- middle of the frame suitable for a line's start time. -exact_ms_from_frame = (frame) -> - frame += 1 - - ms = aegisub.ms_from_frame(frame) - while true - new_ms = ms - 1 - if new_ms < 0 or aegisub.frame_from_ms(new_ms) != frame - break - - ms = new_ms - - return ms - 1 - --- line2fbf function, modified from a function by PhosCity -line2fbf = (sourceData, cleanLevel = 3) -> - line, effTags = sourceData.line, (sourceData\getEffectiveTags -1, true, true, false).tags - -- Aegisub will never give us timestamps that aren't rounded to centiseconds, but lua code might. - -- Explicitly round to centiseconds just to be sure. - startTime = round_to_cs line.start_time - startFrame = line.startFrame - endFrame = line.endFrame - - -- Tag Collection - local fade - -- Fade - for tag in *{"fade_simple", "fade"} - fade = sourceData\getTags(tag, 1)[1] - break if fade - - -- Transform - transforms = sourceData\getTags "transform" - tagSections = {} - sectionEffTags = {} - sourceData\callback ((section, _, i, j) -> - tagSections[i] = j - sectionEffTags[i] = (section\getEffectiveTags true).tags - ), ASS.Section.Tag - - -- Fbfing - fbfLines = {} - for frame = startFrame, endFrame-1 - newLine = Line sourceData.line, sourceData.line.parentCollection - newLine.start_time = aegisub.ms_from_frame(frame) - newLine.end_time = aegisub.ms_from_frame(frame + 1) - data = ASS\parse newLine - now = exact_ms_from_frame(frame) - startTime - - -- Move - move = effTags.move - if move and not move.startPos\equal move.endPos - t1, t2 = move.startTime.value, move.endTime.value - - -- Does assf handle this for us already? Who knows, certainly not me! - t1 or= 0 - t2 or= 0 - - t1, t2 = t2, t1 if t1 > t2 - - if t1 <= 0 and t2 <= 0 - t1 = 0 - t2 = line.duration - - local k - if now <= t1 - k = 0 - elseif now >= t2 - k = 1 - else - k = (now - t1) / (t2 - t1) - - finalPos = move.startPos\lerp(move.endPos, k) - data\removeTags "move" - data\replaceTags {ASS\createTag "position", finalPos} - - -- Transform - if #transforms > 0 - currValue = {} - data\removeTags "transform" - for tr in *transforms - sectionIndex = tr.parent.index - tagIndex = tagSections[sectionIndex] - - t1 = tr.startTime\get! - t2 = tr.endTime\get! - - t2 = line.duration if t2 == 0 - - accel = tr.accel\get! or 1 - - local k - if now < t1 - k = 0 - elseif now >= t2 - k = 1 - else - k = ((now - t1) / (t2 - t1))^accel - - for tag in *tr.tags\getTags! - -- FIXME this still breaks in a case like \t(\frx10)\frx20\t(\frx30) - -- but that's extremely niche so I'm not fixing it now - tagname = tag.__tag.name - currValue[tagIndex] or= {} - currValue[tagIndex][tagname] or= sectionEffTags[sectionIndex][tagname] - local finalValue - - if tag.class == ASS.Tag.Color - finalValue = currValue[tagIndex][tagname]\copy! - for channel in *{"r", "g", "b"} - finalValue[channel] = finalValue[channel]\lerp tag[channel], k - elseif tag.class == ASS.Tag.ClipRect - -- ClipRect\lerp exists but does not return the resulting clip. If and when this gets fixed, this can be removed - finalValue = currValue[tagIndex][tagname]\copy! - for pt in *{"topLeft", "bottomRight"} - finalValue[pt] = finalValue[pt]\lerp tag[pt], k - else - finalValue = currValue[tagIndex][tagname]\lerp tag, k - - data\replaceTags finalValue, tagIndex, tagIndex, true - currValue[tagIndex][tagname] = finalValue - - -- Fade - if fade - local a1, a2, a3, t1, t2, t3, t4 - if fade.__tag.name == "fade_simple" - a1, a2, a3 = 255, 0, 255 - t1, t4 = -1, -1 - t2, t3 = fade.inDuration\getTagParams!, fade.outDuration\getTagParams! - else - a1, a2, a3, t1, t2, t3, t4 = fade\getTagParams! - - if t1 == -1 and t4 == -1 - t1 = 0 - t4 = line.duration - t3 = t4 - t3 - - local fadeVal - if now < t1 - fadeVal = a1 - elseif now < t2 - k = (now - t1)/(t2 - t1) - fadeVal = a1 * (1 - k) + a2 * k - elseif now < t3 - fadeVal = a2 - elseif now < t4 - k = (now - t3)/(t4 - t3) - fadeVal = a2 * (1 - k) + a3 * k - else - fadeVal = a3 - - data\removeTags {"fade", "fade_simple"} - - -- Insert all alpha tags so we can modify them later - -- Don't bother with checking if they exist already, cleanTags will do that for us later - alphaTags = data\getDefaultTags!\filterTags [ "alpha#{k}" for k=1,4 ] - if alphaTags.tags.alpha1.value == alphaTags.tags.alpha2.value and alphaTags.tags.alpha1.value == alphaTags.tags.alpha3.value and alphaTags.tags.alpha1.value == alphaTags.tags.alpha4.value - alphaTags = {ASS\createTag "alpha", alphaTags.tags.alpha1.value} - - data\insertTags alphaTags, 1, 1 - - data\modTags {"alpha", "alpha1", "alpha2", "alpha3", "alpha4"}, ((tag) -> - tag.value = tag.value - (tag.value * fadeVal - 0x7F) / 0xFF + fadeVal - tag.value = math.max(0, math.min(255, tag.value)) - ) if fadeVal > 0 - - data\cleanTags cleanLevel - data\commit! - table.insert fbfLines, newLine - - return fbfLines - track = (quads, options, subs, sel, active) -> lines = LineCollection subs, sel, () -> true @@ -221,7 +44,7 @@ track = (quads, options, subs, sel, active) -> table.insert to_delete, line - fbf = line2fbf data + fbf = Util.line2fbf data for fbfline in *fbf lines\addLine fbfline ), true diff --git a/modules/arch/Util.moon b/modules/arch/Util.moon new file mode 100644 index 0000000..9d248c0 --- /dev/null +++ b/modules/arch/Util.moon @@ -0,0 +1,208 @@ +DependencyControl = require 'l0.DependencyControl' + +depctrl = DependencyControl { + name: "Util", + version: "0.1.0", + description: [[Utility functions used in some of my scripts]], + author: "arch1t3cht", + url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts", + moduleName: 'arch.Util', + { + {"a-mo.Line", version: "1.5.3", 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"}, + } +} + +Line, ASS = depctrl\requireModules! + + +-- rounds a ms timestamp to cs just like Aegisub does +round_to_cs = (time) -> + (time + 5) - (time + 5) % 10 + + +-- gets the exact starting timestamp of a given frame, +-- unlike aegisub.frame_from_ms, which returns a timestamp in the +-- middle of the frame suitable for a line's start time. +exact_ms_from_frame = (frame) -> + frame += 1 + + ms = aegisub.ms_from_frame(frame) + while true + new_ms = ms - 1 + if new_ms < 0 or aegisub.frame_from_ms(new_ms) != frame + break + + ms = new_ms + + return ms - 1 + + +-- line2fbf function, modified from a function by PhosCity +line2fbf = (sourceData, cleanLevel = 3) -> + line, effTags = sourceData.line, (sourceData\getEffectiveTags -1, true, true, false).tags + -- Aegisub will never give us timestamps that aren't rounded to centiseconds, but lua code might. + -- Explicitly round to centiseconds just to be sure. + startTime = round_to_cs line.start_time + startFrame = line.startFrame + endFrame = line.endFrame + + -- Tag Collection + local fade + -- Fade + for tag in *{"fade_simple", "fade"} + fade = sourceData\getTags(tag, 1)[1] + break if fade + + -- Transform + transforms = sourceData\getTags "transform" + tagSections = {} + sectionEffTags = {} + sourceData\callback ((section, _, i, j) -> + tagSections[i] = j + sectionEffTags[i] = (section\getEffectiveTags true).tags + ), ASS.Section.Tag + + -- Fbfing + fbfLines = {} + for frame = startFrame, endFrame-1 + newLine = Line sourceData.line, sourceData.line.parentCollection + newLine.start_time = aegisub.ms_from_frame(frame) + newLine.end_time = aegisub.ms_from_frame(frame + 1) + data = ASS\parse newLine + now = exact_ms_from_frame(frame) - startTime + + -- Move + move = effTags.move + if move and not move.startPos\equal move.endPos + t1, t2 = move.startTime.value, move.endTime.value + + -- Does assf handle this for us already? Who knows, certainly not me! + t1 or= 0 + t2 or= 0 + + t1, t2 = t2, t1 if t1 > t2 + + if t1 <= 0 and t2 <= 0 + t1 = 0 + t2 = line.duration + + local k + if now <= t1 + k = 0 + elseif now >= t2 + k = 1 + else + k = (now - t1) / (t2 - t1) + + finalPos = move.startPos\lerp(move.endPos, k) + data\removeTags "move" + data\replaceTags {ASS\createTag "position", finalPos} + + -- Transform + if #transforms > 0 + currValue = {} + data\removeTags "transform" + for tr in *transforms + sectionIndex = tr.parent.index + tagIndex = tagSections[sectionIndex] + + t1 = tr.startTime\get! + t2 = tr.endTime\get! + + t2 = line.duration if t2 == 0 + + accel = tr.accel\get! or 1 + + local k + if now < t1 + k = 0 + elseif now >= t2 + k = 1 + else + k = ((now - t1) / (t2 - t1))^accel + + for tag in *tr.tags\getTags! + -- FIXME this still breaks in a case like \t(\frx10)\frx20\t(\frx30) + -- but that's extremely niche so I'm not fixing it now + tagname = tag.__tag.name + currValue[tagIndex] or= {} + currValue[tagIndex][tagname] or= sectionEffTags[sectionIndex][tagname] + local finalValue + + if tag.class == ASS.Tag.Color + finalValue = currValue[tagIndex][tagname]\copy! + for channel in *{"r", "g", "b"} + finalValue[channel] = finalValue[channel]\lerp tag[channel], k + elseif tag.class == ASS.Tag.ClipRect + -- ClipRect\lerp exists but does not return the resulting clip. If and when this gets fixed, this can be removed + finalValue = currValue[tagIndex][tagname]\copy! + for pt in *{"topLeft", "bottomRight"} + finalValue[pt] = finalValue[pt]\lerp tag[pt], k + else + finalValue = currValue[tagIndex][tagname]\lerp tag, k + + data\replaceTags finalValue, tagIndex, tagIndex, true + currValue[tagIndex][tagname] = finalValue + + -- Fade + if fade + local a1, a2, a3, t1, t2, t3, t4 + if fade.__tag.name == "fade_simple" + a1, a2, a3 = 255, 0, 255 + t1, t4 = -1, -1 + t2, t3 = fade.inDuration\getTagParams!, fade.outDuration\getTagParams! + else + a1, a2, a3, t1, t2, t3, t4 = fade\getTagParams! + + if t1 == -1 and t4 == -1 + t1 = 0 + t4 = line.duration + t3 = t4 - t3 + + local fadeVal + if now < t1 + fadeVal = a1 + elseif now < t2 + k = (now - t1)/(t2 - t1) + fadeVal = a1 * (1 - k) + a2 * k + elseif now < t3 + fadeVal = a2 + elseif now < t4 + k = (now - t3)/(t4 - t3) + fadeVal = a2 * (1 - k) + a3 * k + else + fadeVal = a3 + + data\removeTags {"fade", "fade_simple"} + + -- Insert all alpha tags so we can modify them later + -- Don't bother with checking if they exist already, cleanTags will do that for us later + alphaTags = data\getDefaultTags!\filterTags [ "alpha#{k}" for k=1,4 ] + if alphaTags.tags.alpha1.value == alphaTags.tags.alpha2.value and alphaTags.tags.alpha1.value == alphaTags.tags.alpha3.value and alphaTags.tags.alpha1.value == alphaTags.tags.alpha4.value + alphaTags = {ASS\createTag "alpha", alphaTags.tags.alpha1.value} + + data\insertTags alphaTags, 1, 1 + + data\modTags {"alpha", "alpha1", "alpha2", "alpha3", "alpha4"}, ((tag) -> + tag.value = tag.value - (tag.value * fadeVal - 0x7F) / 0xFF + fadeVal + tag.value = math.max(0, math.min(255, tag.value)) + ) if fadeVal > 0 + + data\cleanTags cleanLevel + data\commit! + table.insert fbfLines, newLine + + return fbfLines + + +lib = { + :round_to_cs, + :exact_ms_from_frame, + :line2fbf, +} + +lib.version = depctrl +return depctrl\register lib