dotfiles/.aegisub/automation/autoload/a-mo.Aegisub-Motion.moon

728 lines
31 KiB
Plaintext

-- See LICENSE for more info about your rights as a person to be
-- rightfully persecuted
export script_name = "Aegisub-Motion"
export script_description = "A set of tools for simplifying the process of creating and applying motion tracking data with Aegisub."
export script_author = "torque"
export script_version = "1.0.9"
export script_namespace = "a-mo.Aegisub-Motion"
local interface, setProgress, setTask
local versionRecord, clipboard, json, ConfigHandler, DataWrapper
local LineCollection, log, Math, MotionHandler, Statistics, TrimHandler, Tags
haveDepCtrl, DependencyControl = pcall require, "l0.DependencyControl"
if haveDepCtrl
versionRecord = DependencyControl {
url: 'https://github.com/TypesettingTools/Aegisub-Motion'
feed: 'https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json'
{
'aegisub.clipboard'
'json'
{ 'a-mo.ConfigHandler', version: '1.1.4' }
{ 'a-mo.DataWrapper', version: '1.0.2' }
{ 'a-mo.LineCollection', version: '1.3.0' }
{ 'a-mo.Log' , version: '1.0.0' }
{ 'a-mo.Math' , version: '1.0.0' }
{ 'a-mo.MotionHandler', version: '1.1.8' }
{ 'a-mo.Statistics' , version: '0.1.3' }
{ 'a-mo.TrimHandler', version: '1.0.5' }
{ 'a-mo.Tags', version: '1.3.4' }
}
}
clipboard, json, ConfigHandler, DataWrapper, LineCollection, log, Math, MotionHandler, Statistics, TrimHandler, Tags = versionRecord\requireModules!
else
clipboard = require 'aegisub.clipboard'
json = require 'json'
ConfigHandler = require 'a-mo.ConfigHandler'
DataWrapper = require 'a-mo.DataWrapper'
LineCollection = require 'a-mo.LineCollection'
log = require 'a-mo.Log'
Math = require 'a-mo.Math'
MotionHandler = require 'a-mo.MotionHandler'
Statistics = require 'a-mo.Statistics'
TrimHandler = require 'a-mo.TrimHandler'
Tags = require 'a-mo.Tags'
statsTemplate = {
apply: {
longestTrack: 0
lines: { largestInput: 0, largestOutput: 0, totalOutput: 0 }
bytes: { largestInput: 0, largestOutput: 0, totalOutput: 0 }
runCount: 0
}
trim: {
runCount: 0
clipsCreated: 0
}
trimEach: {
runCount: 0
}
revert: {
lines: { total: 0 }
bytes: { total: 0 }
runCount: 0
}
uuid: 0
}
initStats = ->
stats = Statistics statsTemplate, "aegisub-motion.stats.json"
if 0 == stats\getValue "uuid"
stats\setValue "uuid", Math.uuid!
stats\write!
return stats
initializeInterface = ->
-- Set up interface tables.
interface = {
main: {
-- mnemonics: xyOCSBuRWen + G\A + Wl\A
dataLabel: { class: "label", x: 0, y: 0, width: 10, height: 1, label: " Paste data or enter a filepath." }
data: { class: "textbox", x: 0, y: 1, width: 10, height: 4, hint: "Paste data or the path to a file containing it. No quotes or escapes." }
-- optLabel: { class: "label", x: 0, y: 5, width: 5, height: 1, label: "Data to be applied:" }
xPosition: { class: "checkbox", x: 0, y: 5, width: 1, height: 1, config: true, label: "&x", value: true, hint: "Apply x position data to the selected lines." }
yPosition: { class: "checkbox", x: 1, y: 5, width: 1, height: 1, config: true, label: "&y", value: true, hint: "Apply y position data to the selected lines." }
origin: { class: "checkbox", x: 2, y: 5, width: 2, height: 1, config: true, label: "&Origin", value: false, hint: "Move the origin along with the position." }
absPos: { class: "checkbox", x: 4, y: 5, width: 2, height: 1, config: true, label: "Absolut&e", value: false, hint: "Set position to exactly that of the tracking data with no processing." }
xScale: { class: "checkbox", x: 0, y: 6, width: 2, height: 1, config: true, label: "&Scale", value: true, hint: "Apply scaling data to the selected lines." }
border: { class: "checkbox", x: 2, y: 6, width: 2, height: 1, config: true, label: "&Border", value: true, hint: "Scale border with the line (only if Scale is also selected)." }
shadow: { class: "checkbox", x: 4, y: 6, width: 2, height: 1, config: true, label: "&Shadow", value: true, hint: "Scale shadow with the line (only if Scale is also selected)." }
blur: { class: "checkbox", x: 4, y: 7, width: 2, height: 1, config: true, label: "Bl&ur", value: true, hint: "Scale blur with the line (only if Scale is also selected, does not scale \\be)." }
blurScale: { class:"floatedit", x: 7, y: 7, width: 2, height: 1, config: true, step: 0.01, value: 1, hint: "Factor to attenuate (or amplify) blur values by." }
zRotation: { class: "checkbox", x: 0, y: 7, width: 3, height: 1, config: true, label: "&Rotation", value: false, hint: "Apply rotation data to the selected lines." }
writeConf: { class: "checkbox", x: 0, y: 10, width: 4, height: 1, config: true, label: "&Write config", value: true, hint: "Write current settings to the configuration file." }
relative: { class: "checkbox", x: 4, y: 10, width: 3, height: 1, config: true, label: "Relat&ive", value: true, hint: "Start frame should be relative to the beginning of the selection rather than the beginning of the video." }
startFrame:{ class: "intedit", x: 7, y: 10, width: 2, height: 1, config: true, value: 1, hint: "Frame used as the starting point for the tracking data. \"-1\" corresponds to the last frame." }
linear: { class: "checkbox", x: 4, y: 11, width: 2, height: 1, config: true, label: "Li&near", value: false, hint: "Use transforms and \\move to create a linear transition, instead of frame-by-frame." }
clipOnly: { class: "checkbox", x: 0, y: 11, width: 3, height: 1, config: true, label: "&Clip Only", value: false, hint: "Only apply the main data to \\clips present in the line." }
rectClip: { class: "checkbox", x: 0, y: 8, width: 3, height: 1, config: true, label: "Rect C&lip", value: true, hint: "Apply tracking data to the rectangular clip contained in the line." }
vectClip: { class: "checkbox", x: 3, y: 8, width: 3, height: 1, config: true, label: "&Vect Clip", value: true, hint: "Apply tracking data to the vector clip contained in the line." }
rcToVc: { class: "checkbox", x: 6, y: 8, width: 4, height: 1, config: true, label: "Rect -> Vect", value: false, hint: "Convert rectangular clips contained in the line to vector clips." }
killTrans: { class: "checkbox", x: 0, y: 9, width: 10, height: 1, config: true, label: "Interpolate &transforms", value: true, hint: "Attempt to interpolate transform value instead of just shifting transform times." }
}
clip: {
-- mnemonics: xySRe + GCA
dataLabel: { class: "label", x: 0, y: 0, width: 10, height: 1, label: " This stuff is for clips." }
data: { class: "textbox", x: 0, y: 1, width: 10, height: 4, name: "data", hint: "Paste data or the path to a file containing it. No quotes or escapes." }
optLabel: { class: "label", x: 0, y: 5, width: 5, height: 1, label: "Data to be applied:" }
xPosition: { class: "checkbox", x: 0, y: 6, width: 1, height: 1, config: true, label: "&x", value: true, hint: "Apply x position data to the selected clips in the selected lines." }
yPosition: { class: "checkbox", x: 1, y: 6, width: 1, height: 1, config: true, label: "&y", value: true, hint: "Apply y position data to the selected clips in the selected lines." }
xScale: { class: "checkbox", x: 0, y: 7, width: 2, height: 1, config: true, label: "&Scale", value: true, hint: "Apply scaling data to the selected clips in the selected lines." }
zRotation: { class: "checkbox", x: 0, y: 8, width: 3, height: 1, config: true, label: "&Rotation", value: false, hint: "Apply rotation data to the selected clips in the selected lines." }
rectClip: { class: "checkbox", x: 0, y: 10, width: 3, height: 1, config: true, label: "Rect C&lip", value: true, hint: "Apply tracking data to the rectangular clip contained in the line." }
vectClip: { class: "checkbox", x: 3, y: 10, width: 3, height: 1, config: true, label: "&Vect Clip", value: true, hint: "Apply tracking data to the vector clip contained in the line." }
rcToVc: { class: "checkbox", x: 6, y: 10, width: 4, height: 1, config: true, label: "Rect -> Vect", value: false, hint: "Convert rectangular clips contained in the line to vector clips." }
startLabel:{ class: "label", x: 7, y: 5, width: 3, height: 1, label: "Start Frame:" }
startFrame:{ class: "intedit", x: 7, y: 6, width: 3, height: 1, config: true, value: 1, hint: "Frame used as the starting point for the tracking data. \"-1\" corresponds to the last frame." }
}
trim: {
pLabel: { class: "label", x: 0, y: 0, width: 10, height: 1, label: [[
Prefix the encoded video is written. Useful values are ?video for the
directory of the currently loaded video and ?script for the directory
of the currently open script. Can be a hardcoded path, too.]] }
psLabel: { class: "label", x: 0, y: 3, width: 10, height: 1, label: [[
Encoding preset. Different presets may have different output.]] }
eLabel: { class: "label", x: 0, y: 5, width: 10, height: 1, label: [[
The currently selected encoding binary. Use the "Encoder..." button
below to set this. Manual edits will not be saved.]] }
cLabel: { class: "label", x: 0, y: 7, width: 10, height: 1, label: [[
If you want to use a custom encoding command, write it here. Leave this
blank to use the built-in presets (probably what you want).]] }
prefix: { config: true, value: "?video", class: "textbox", x: 0, y: 1, width: 10, height: 1, hint: "Prefix the encoded video is written. Useful values are ?video for the directory of the currently loaded video and ?script for the directory of the currently open script." }
makePfix: { config: true, value: true, class: "checkbox", x: 0, y: 2, width: 10, height: 1, label: "Try to create prefix directory.", hint: "Try to create prefix directory." }
preset: { config: true, value: "x264", class: "dropdown", x: 0, y: 4, width: 10, height: 1, label: "Sort lines by", items: TrimHandler.existingPresets, hint: "Choose an existing preset by name." }
encBin: { config: true, value: "", class: "textbox", x: 0, y: 6, width: 10, height: 1, hint: "The full path to your encoding binary (x264.exe if you're using the default preset)" }
command: { config: true, value: "", class: "textbox", x: 0, y: 8, width: 10, height: 4, hint: "If you want to use a custom encoding command, write it here." }
}
}
if TrimHandler.windows
interface.trim.writeLog = { config: true, value: true, class: "checkbox", x: 0, y: 12, width: 10, height: 1, label: "Write encode log.", hint: "Write encode log. Allows aegisub-motion to print information if an encode fails. Only matters on Windows." }
interface
fetchDataFromClipboard = ->
-- According to vague reports, Aegisub clipboard usage crashes it on
-- Linux. Disable any clipboard use until I find a better way to
-- handle this.
dataString = (jit.os != "Linux") and clipboard.get!
-- If there's nothing on the clipboard, clipboard.get returns nil.
return dataString or ""
prepareConfig = ( config, mainData, clipData, lineCollection ) ->
rectClipData, vectClipData = nil, nil
totalFrames = lineCollection.totalFrames
for field, data in pairs { main: mainData, clip: clipData }
configField = config[field]
if '' == configField.data or nil == configField.data
for option in pairs configField
-- Nuke everything because it doesn't matter at this point.
configField[option] = false
else
-- Be extremely lazy and just re-parse data from scratch.
unless data\bestEffortParsingAttempt configField.data, lineCollection.meta.PlayResX, lineCollection.meta.PlayResY
log.windowError "You put something in the #{field} data box\nbut it is wrong in ways I can't imagine."
unless data.dataObject\checkLength totalFrames
log.windowError "The length of your #{field} data (#{data.dataObject.length} frames) doesn't match\nthe length of your lines (#{totalFrames} frames) and I quit."
if configField.rcToVc
configField.rectClip = true
configField.vectClip = true
if configField.rectClip
rectClipData = data
if configField.vectClip
vectClipData = data
unless config.main.data or config.clip.data
log.windowError "As far as I can tell, you've forgotten to give me any motion data."
-- Disable options that depend on scale.
unless config.main.xScale
config.main.border = false
config.main.shadow = false
config.main.blur = false
-- Nudge the start frames.
if config.main.relative
for context in *{ 'main', 'clip' }
-- Have to check that the field exists (if the clip dialog wasn't
-- opened it will be nil) to avoid comparison with nil errors.
if config[context].startFrame
if config[context].startFrame == 0
config[context].startFrame = 1
elseif config[context].startFrame < 0
config[context].startFrame = totalFrames + config[context].startFrame + 1
else
for context in *{ 'main', 'clip' }
if config[context].startFrame
config[context].startFrame = config[context].startFrame - lineCollection.startFrame + 1
if config[context].startFrame <= 0
log.windowError "You have specified an out-of-range absolute\nstart frame and you have been judged."
return rectClipData, vectClipData
-- Used in getMissingTags, for fade handling when killTrans is used
getMissingAlphas = ( block, properties ) ->
-- makes sure every necessary alpha tag exists in the first block
alpha = Tags.allTags.alpha
alpha1, alpha2, alpha3, alpha4 = Tags.allTags.alpha1, Tags.allTags.alpha2, Tags.allTags.alpha3, Tags.allTags.alpha4
outline, shadow = Tags.allTags.border, Tags.allTags.shadow
if block\find alpha.pattern
return ''
if ( properties[alpha1] == 0 and properties[alpha2] == 0 and
properties[alpha3] == 0 and properties[alpha4] == 0 )
return alpha\format 0
alphas = { }
if not block\find alpha1.pattern
table.insert alphas, alpha1\format properties[alpha1]
if ( not block\find alpha2.pattern ) and ( block\find Tags.allTags.karaoke.pattern )
table.insert alphas, alpha2\format properties[alpha2]
if ( not block\find alpha3.pattern ) and ( ( block\find "\\[xy]?bord([%d%.]+)" ) or ( properties[outline] > 0 ) )
table.insert alphas, alpha3\format properties[alpha3]
if ( not block\find alpha4.pattern ) and ( ( block\find "\\[xy]?shad([%d%.]+)" ) or ( properties[shadow] > 0 ) )
table.insert alphas, alpha4\format properties[alpha4]
return table.concat alphas, ''
-- This table 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 = { opt:"opt", skip:val } where
-- "opt" is the option that must be enabled and skip specifies
-- not to write the tag if the style default is that value.
importantTags = {
xscale: { opt: "xScale", skip: 0 }
yscale: { opt: "xScale", skip: 0 }
border: { opt: "border", skip: 0 }
shadow: { opt: "shadow", skip: 0 }
zrot: { opt: "zRotation" }
}
-- A style table is passed to this function so that it can cope with
-- \r.
getMissingTags = ( block, options, properties ) ->
result = { }
for key, tab in pairs importantTags
tag = Tags.allTags[key]
if options[tab.opt]
if not block\match tag.pattern
if properties[tag] != tab.skip
table.insert result, tag\format properties[tag]
if options.killTrans
table.insert result, getMissingAlphas block, properties
return table.concat result, ''
rectClipToVectClip = ( clip ) ->
if clip\match "[%-%d%.]+, *[%-%d%.]+"
clip = clip\gsub "([%-%d%.]+), *([%-%d%.]+), *([%-%d%.]+), *([%-%d%.]+)", ( l, t, r, b ) ->
return table.concat ({
"m %g %g "\format l, t
"l %g %g "\format r, t
"%g %g "\format r, b
"%g %g"\format l, b
})
return clip
convertClipToFP = ( clip ) ->
-- only muck around with vector clips (convert scaling factor into floating point coordinates).
unless clip\match "[%-%d%.]+, *[%-%d%.]+"
-- Convert clip with scale into floating point coordinates.
clip = clip\gsub "%((%d*),?(.-)%)", ( scaleFactor, points ) ->
if scaleFactor ~= ""
scaleFactor = tonumber scaleFactor
points = points\gsub "([%.%d%-]+) ([%.%d%-]+)", ( x, y ) ->
x = Math.round tonumber( x )/(2^(scaleFactor - 1)), 2
y = Math.round tonumber( y )/(2^(scaleFactor - 1)), 2
("%g %g")\format x, y
return '(' .. points .. ')'
return clip
prepareLines = ( lineCollection ) ->
setProgress 0
totalLines = #lineCollection.lines
options = lineCollection.options
-- remove the lines while ensuring new lines will be inserted in the
-- correct place.
lineCollection\deleteLines!
-- Perform all of the manipulation that used to be performed in
-- Line.moon but are actually fairly Aegisub-Motion specific.
lineCollection\runCallback ( line, index ) =>
log.checkCancellation!
-- If a line already contains a-mo extradata, it is probably being
-- tracked again after being tracked. There are some reasons to do
-- this, but it results in a huge amount of extradata garbage if
-- unchecked. Presumably 99% of tracking that isn't this case will
-- be on lines without extradata. This isn't a perfect solution, but
-- it is probably better than before. This doesn't change
-- extradata's lack of resilience toward copying lines between
-- scripts.
if line\getExtraData 'a-mo'
line.retrack = true
else
-- Add our signature extradata.
line\setExtraData 'a-mo', { originalText: line.text, uuid: Math.uuid! }
-- Get default style properties (will be used later if transform
-- interpolation is enabled)
line\getPropertiesFromStyle!
-- need to brutalize fades before tokenizing transforms so that the
-- produced transforms will be tokenized as well. Alternately (this
-- may be more clean but also less efficient), tokenize transforms,
-- dedup tags, detokenize transforms, brutalize fades, and then
-- retokenize transforms.
line\tokenizeTransforms!
line\runCallbackOnOverrides ( tagBlock ) =>
return tagBlock\gsub "\\fade?%((%d+),(%d+)%)", ( start, finish ) ->
return "\\fade(255,0,255,0,%d,%d,%d)"\format start, @duration - finish, @duration
-- retokenize the transforms to simplify later processing.
line\dontTouchTransforms!
line\tokenizeTransforms!
-- Deduplicate all override tags.
line\deduplicateTags!
-- Collect alignment and position info for each line.
styles = @styles
lineStyle = line.styleRef
unless line\extraMetrics lineStyle
-- unless line\moveToPosition math.floor(0.5*(aegisub.ms_from_frame(lineCollection.startFrame + options.main.startFrame - 1) + aegisub.ms_from_frame(lineCollection.startFrame + options.main.startFrame))) - line.start_time
line\ensureLeadingOverrideBlockExists!
-- Note that we are repeatedly shadowing @, so in this function it
-- refers to the line. This is interestingly the opposite of how
-- fat arrow functions work in coffeescript.
line\runCallbackOnFirstOverride ( tagBlock ) =>
return tagBlock\gsub "{", ("{\\pos(%g,%g)")\format @xPosition, @yPosition
-- Add any tags we need that are missing from the line.
line\runCallbackOnFirstOverride ( tagBlock ) =>
tags = getMissingTags tagBlock, options.main, line.properties
return '{' .. tags .. tagBlock\sub( 2 )
line\runCallbackOnOverrides ( tagBlock ) =>
tagBlock\gsub "\\org%([%.%d%-]+,[%.%d%-]+%)", ->
@hasOrg = true
return nil
savestyle = line.styleRef
reset = false
tagBlock = tagBlock\gsub "\\r([^\\}]*)([^}]*)", ( resetStyle, remainder ) ->
if styles[resetStyle]
line.styleRef = styles[resetStyle]
line\getPropertiesFromStyle!
reset = true
tags = getMissingTags remainder, options.main, line.properties
return '\\r' .. resetStyle .. tags .. remainder
if reset
line.styleRef = savestyle
line\getPropertiesFromStyle!
if options.main.rectClip or options.main.vectClip or options.clip.rectClip or options.clip.vectClip
tagBlock = tagBlock\gsub "(\\i?clip%b())", ( clip ) ->
@hasClip = true
clip = convertClipToFP clip
if options.main.rcToVc or options.clip.rcToVc
clip = rectClipToVectClip clip
return clip
return tagBlock
unless line.hasClip
line\runCallbackOnFirstOverride ( tagBlock ) =>
"{\\clip()" .. tagBlock\sub 2
setProgress index/totalLines*100
postprocLines = ( lineCollection ) ->
setProgress 0
totalLines = #lineCollection.lines
lineCollection\runCallback ( line, index ) =>
if line.wasLinear
line\dontTouchTransforms!
else
line\deduplicateTags!
line\shiftKaraoke!
line.text = line.text\gsub "{}", ""
setProgress index/totalLines*100
log.checkCancellation!
-- No progress for this.
lineCollection\combineIdenticalLines!
applyProcessor = ( subtitles, selectedLines ) ->
setTask = aegisub.progress.task
setProgress = aegisub.progress.set
setTask "Loading Interface"
initializeInterface!
math.randomseed tonumber tostring( os.time! )\reverse!\sub( 1, 8 )
-- Initialize the configuration
options = ConfigHandler interface, "aegisub-motion.json", true, script_version
options\read!
options\updateInterface { "main", "clip" }
stats = initStats!
lineCollection = LineCollection subtitles, selectedLines
currentVideoFrame = aegisub.project_properties!.video_position
rawInputData = fetchDataFromClipboard!
-- Instantiate both of these so they can be passed by reference later.
setTask "Checking Clipboard for Data"
mainData = DataWrapper!
clipData = DataWrapper!
if mainData\bestEffortParsingAttempt rawInputData, lineCollection.meta.PlayResX, lineCollection.meta.PlayResY
if mainData.dataObject\checkLength lineCollection.totalFrames
interface.main.data.value = rawInputData
interface.main.dataLabel.label = " Data is the correct length."
else
interface.main.dataLabel.label = "Data had the wrong framecount. Expected: #{lineCollection.totalFrames}. Got: #{mainData.dataObject.length}"
if options.configuration.main.relative
relativeFrame = currentVideoFrame - lineCollection.startFrame + 1
if relativeFrame > 0 and relativeFrame <= lineCollection.totalFrames
interface.main.startFrame.value = relativeFrame
interface.clip.startFrame.value = relativeFrame
else
interface.main.startFrame.value = currentVideoFrame
interface.clip.startFrame.value = currentVideoFrame
setTask "Launching Interface"
-- cancel:Abort in the main dialog tells Esc key to abort the entire macro
-- cancel:Back in \clip dialog tells Esc key to close it and go back to the main dialog
buttons = {
main: {
list: {
"&Go"
"Track &\\clip separately"
"&Quit"
}
namedList: {
ok: "&Go"
clip: "Track &\\clip separately"
cancel: "&Quit"
}
}
clip: {
list: {
"&Go"
"&Back"
"&Quit"
}
namedList: {
ok: "&Go"
close: "&Back"
abort: "&Quit"
}
}
}
currentDialog = "main"
config = { clip: { }, main: { } }
while true
button, config[currentDialog] = aegisub.dialog.display interface[currentDialog], buttons[currentDialog].list, buttons[currentDialog].namedList
for k, v in pairs config[currentDialog]
interface[currentDialog][k].value = v
switch button
when buttons.main.namedList.clip
currentDialog = "clip"
when false, buttons.clip.namedList.abort
setTask "ABORT"
aegisub.cancel!
when buttons.clip.namedList.close
currentDialog = "main"
else
break
-- Update the persistent configuration before it gets (potentially)
-- horribly mutilated in prepareConfig. Ensures that what the user saw
-- last is what will be presented to them next time.
if config.main.writeConf
setTask "Updating Configuration"
options\updateConfiguration config, { "main", "clip" }
if config.main.writeConf or (options.configuration.main.writeConf != config.main.writeConf)
options.configuration.main.writeConf = config.main.writeConf
options\write!
setTask "Preparing Configuration and Data"
rectClipData, vectClipData = prepareConfig config, mainData, clipData, lineCollection
lineCollection.options = config
setTask "Preprocessing Lines"
prepareLines lineCollection
stats\setMax "apply.longestTrack", lineCollection.totalFrames
stats\setMax "apply.lines.largestInput", #lineCollection.lines
for line in *lineCollection.lines
stats\setMax "apply.bytes.largestInput", #line.text
if mainData.type and 'SRS' != mainData.type
mainData.dataObject\addReferenceFrame config.main.startFrame
mainData.dataObject\stripFields config.main
if clipData.type and 'SRS' != clipData.type
clipData.dataObject\addReferenceFrame config.clip.startFrame
clipData.dataObject\stripFields config.clip
setTask "Applying Data"
motionHandler = MotionHandler lineCollection, mainData, rectClipData, vectClipData
newLines = motionHandler\applyMotion!
-- Postproc lines: detokenize transforms and combine identical lines.
setTask "Postprocessing Lines"
postprocLines newLines
newLines\replaceLines!
stats\setMax "apply.lines.largestOutput", #newLines.lines
stats\incrementValue "apply.lines.totalOutput", #newLines.lines
for line in *newLines.lines
stats\setMax "apply.bytes.largestOutput", #line.text
stats\incrementValue "apply.bytes.totalOutput", #line.text
stats\incrementValue "apply.runCount"
stats\write!
trimConfigDialog = ( options ) ->
buttons = {
{ "&Save", "&Encoder...", "&Cancel" },
{ ok: "&Save", enc: "&Encoder...", cancel: "&Cancel" }
}
while true
options\updateInterface "trim"
button, config = aegisub.dialog.display interface.trim, buttons[1], buttons[2]
if button == buttons[2].ok or button == buttons[2].enc
-- only update encBin when the open dialog is shown.
config.encBin = nil
options\updateConfiguration config, "trim"
if button == buttons[2].ok
options\write!
break
else
encoder = aegisub.dialog.open "Choose an Encoding Binary", "", "", "", false, true
if encoder
options\updateConfiguration { encBin: encoder }, "trim"
else
aegisub.cancel!
trimConfigurator = ->
initializeInterface!
options = ConfigHandler interface, "aegisub-motion.json", true, script_version
options\read!
trimConfigDialog options
trimProcessor = ( subtitles, selectedLines, activeLine, eachFlag ) ->
setTask = aegisub.progress.task
setProgress = aegisub.progress.set
initializeInterface!
options = ConfigHandler interface, "aegisub-motion.json", true, script_version
stats = initStats!
options\read!
-- Check if encBin has been set.
if options.configuration.trim.encBin == ""
interface.trim.pLabel.label = [[
You must specify the path to your encoding binary.
]] .. interface.trim.pLabel.label
trimConfigDialog options
trim = TrimHandler options.configuration.trim
if eachFlag
seenRanges = { }
for lineIndex in *selectedLines
lineCollection = LineCollection subtitles, { lineIndex }
collectionRange = "#{lineCollection.startFrame}-#{lineCollection.endFrame}"
unless seenRanges[collectionRange]
seenRanges[collectionRange] = true
trim\calculateTrimLength lineCollection
trim\performTrim!
stats\incrementValue "trim.clipsCreated"
stats\incrementValue "trimEach.runCount"
else
lineCollection = LineCollection subtitles, selectedLines
trim\calculateTrimLength lineCollection
trim\performTrim!
stats\incrementValue "trim.clipsCreated"
stats\incrementValue "trim.runCount"
stats\write!
return selectedLines
trimProcessorEach = ( subtitles, selectedLines ) ->
trimProcessor subtitles, selectedLines, nil, true
revertProcessor = ( subtitles, selectedLines ) ->
setTask = aegisub.progress.task
setProgress = aegisub.progress.set
stats = initStats!
setTask "Collecting UUIDs"
-- A table of all UUIDs found in the selected lines
uuids = { }
-- Indices of lines that need to be removed later (are part of a
-- tracked line collection, but are not the first line in that
-- collection)
for index in *selectedLines
with line = subtitles[index]
if .extra['a-mo']
data = json.decode .extra['a-mo']
unless uuids[data.uuid]
.text = data.originalText
.number = index
.extra = {}
uuids[data.uuid] = line
indicesToNuke = { }
setTask "Gathering Matching Lines"
totalLines = #subtitles
for index = 1, totalLines
with line = subtitles[index]
if line.extra
-- Catch lines containing our signature extradata.
if .extra['a-mo']
-- Decode our data, which is stored as json.
data = json.decode .extra['a-mo']
if uuids[data.uuid]
oldLine = uuids[data.uuid]
-- Check if we should change the start time.
if .start_time < oldLine.start_time
oldLine.start_time = .start_time
-- Check if we should change the end time.
if .end_time > oldLine.end_time
oldLine.end_time = .end_time
-- Mark the line for deletion.
if index < oldLine.number
oldLine.number = index
elseif index > oldLine.number
stats\incrementValue "revert.bytes.total", #line.text
indicesToNuke[#indicesToNuke+1] = index
setProgress index/totalLines*100
setTask "Replacing Lines"
-- Replace the lines.
for _, line in pairs uuids
subtitles[line.number] = line
-- Delete the remainders.
subtitles.delete indicesToNuke
stats\incrementValue "revert.lines.total", #indicesToNuke
stats\incrementValue "revert.runCount"
stats\write!
return nil
canRun = ( sub, selectedLines ) ->
if not aegisub.frame_from_ms 0
return false, "You must have a video loaded to run this macro."
elseif 0 == #selectedLines
return false, "You must have lines selected to use this macro."
true
if haveDepCtrl
versionRecord\registerMacros {
{ "Apply", "Applies properly formatted motion tracking data to selected subtitles.", applyProcessor, canRun }
{ "Revert", "Removes properly formatted motion tracking data from selected subtitles.", revertProcessor }
{ "Trim", "Cuts and encodes the current scene for use with motion tracking software.", trimProcessor, canRun }
{ "Trim Each", "Cuts and encodes selected scenes for use with motion tracking software.", trimProcessorEach, canRun }
{ "Trim Settings", "Opens a gui to configure the trim tool.", trimConfigurator }
}
else
aegisub.register_macro "#{script_name}/Apply", "Applies properly formatted motion tracking data to selected subtitles.",
applyProcessor, canRun
aegisub.register_macro "#{script_name}/Revert", "Removes properly formatted motion tracking data from selected subtitles.",
revertProcessor
aegisub.register_macro "#{script_name}/Trim", "Cuts and encodes the current scene for use with motion tracking software.",
trimProcessor, canRun
aegisub.register_macro "#{script_name}/Trim Each", "Cuts and encodes selected scenes for use with motion tracking software.",
trimProcessorEach, canRun
aegisub.register_macro "#{script_name}/Trim Settings", "Opens a gui to configure the trim tool.",
trimConfigurator