Skip to content

Instantly share code, notes, and snippets.

@cN3rd
Last active January 23, 2024 22:57
Show Gist options
  • Save cN3rd/b918ea871b5670c5c87f84c9fd187bdf to your computer and use it in GitHub Desktop.
Save cN3rd/b918ea871b5670c5c87f84c9fd187bdf to your computer and use it in GitHub Desktop.
AfterWaltz v0.4.1
-- MIT License
--
-- Copyright (c) 2017-2024 cN3rd
--
-- 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.
tr = aegisub.gettext
re = require "aegisub.re"
json = require "json"
export script_name = tr"After Waltz"
export script_description = tr"Various utilites for typesetting using after effects"
export script_author = "cN3rd"
export script_version = "0.4.1"
token_description = [[
Allowed Tokens:
{autofold}: Aegisub Automation4 autoload folder (full path)
{end}: End frame (for a given TS)
{start}: Start frame (for a given TS)
{sfold}: Subtitles folder (full path)
{ffmpegExe}: FFmpeg executable (full path)
{vfold}: Video folder
{vpath}: Path to video
Note that the `/` path separator would be converted to the platform's default.
]]
-- "\" on windows, "/" on any other system
pathsep = package.config\sub 1,1
------------
-- utilities
------------
-- log stuff to aegisub
log = (contents) ->
aegisub.log 0, "#{contents}\n"
-- runs a cli comnand
execute = (command) ->
unless debugMode
return os.execute(command)
log command
if os.execute("#{command} > tmp.txt")
debug_contents = read_file("tmp.txt")
log "\n------------------\n#{debug_contents}"
return true, debug_contents
else
log "#{command} failed to run"
return false
-- write a text file using lua I/O
write_file = (path, contents) ->
f = io.open(path, "w")
if not f
return
-- normalize line endings before writing
contents = contents\gsub '\r\n?', '\n'
f\write contents
f\close()
-- reads a text file using lua I/O
read_file = (path) ->
f = io.open(path)
if not f
return nil
contents = f\read "*a"
f\close()
return contents
-- creates a folder recursivley
make_folder = (path) ->
if pathsep == '\\'
execute "md #{path}"
else
execute "mkdir -p #{path}"
-- get containing folder
get_folder_from_path = (path) ->
path\match "(.*#{pathsep})"
-- replaces tokens in a given string
replace_tokens = (str, config, typeset) ->
str = str\gsub "{autofold}", aegisub.decode_path("?data/automation/autoload")
str = str\gsub "{sfold}", aegisub.decode_path("?script")
str = str\gsub "{vfold}", aegisub.decode_path("?video")
str = str\gsub "{vpath}", aegisub.project_properties().video_file
str = str\gsub "{ffmpegExe}", config["ffmpegExePath"]
if typeset != nil
str = str\gsub "{start}", typeset["start_frame"]
str = str\gsub "{end}", typeset["end_frame"]
str = str\gsub "/", pathsep
return str
-------------------------
-- configuration handling
-------------------------
default_config = {
tsTemplate: "{vfold}/Final/{start},{end}.avi",
tcTemplate: "{vfold}/Typecuts/{start},{end}.avi",
ffmpegExePath: "ffmpeg",
scriptPathTemplate: "{vfold}/script.avs"
debugMode: false
}
-- writes the config file using json
write_config = (new_config) ->
write_file aegisub.decode_path("?user/afterwaltz_config.json"), json.encode(new_config)
-- reads the current config
read_config = () ->
contents = read_file(aegisub.decode_path("?user/afterwaltz_config.json"))
if contents == nil
default_config
else
json.decode(contents)
-- lets the user edit the config
show_config_dialogue = () ->
config = read_config()
config_dialogue = {
{width:3, height:1, y:0, x:0, class:"label", label:"FF executeable path"},
{width:20, height:1, y:0, x:5, class:"edit", name:"ffmpegExePath", value: config["ffmpegExePath"]},
{width:3, height:1, y:1, x:0, class:"label", label:"Typecut (raw) naming template:"},
{width:20, height:1, y:1, x:5, class:"edit", name:"tcTemplate", value: config["tcTemplate"]},
{width:3, height:1, y:2, x:0, class:"label", label:"Typeset naming template:"},
{width:20, height:1, y:2, x:5, class:"edit", name:"tsTemplate", value: config["tsTemplate"]},
{width:3, height:1, y:3, x:0, class:"label", label:"Generated script path template:"},
{width:20, height:1, y:3, x:5, class:"edit", name:"scriptPathTemplate", value: config["scriptPathTemplate"]},
{width:10, height:1, y:4, x:0, class:"checkbox", name:"debugMode", label:"Allow debug prints", value: config["debugMode"]},
{width:10, height:1, y:6, x:0, class:"label", label: token_description}
}
btn, result = aegisub.dialog.display(config_dialogue, {"Ok", "Cancel"})
if btn
write_config(result)
-------------------------
-- trimming files
-------------------------
avs_base_script = "# Autogenerated by AfterWaltz
LWLibavVideoSource(\"{vpath}\")
"
avs_trim_script = "#{avs_base_script}
Trim({start}, {end})"
generate_typeset = (line, config) ->
start_frame = aegisub.frame_from_ms line.start_time
end_frame = (aegisub.frame_from_ms line.end_time) - 1
typeset = {
start_frame: start_frame,
end_frame: end_frame,
}
typeset["typeset_name"] = replace_tokens config["tsTemplate"], config, typeset
typeset["typecut_name"] = replace_tokens config["tcTemplate"], config, typeset
return typeset
-- escape paths for vdub scripts
escape_path = (str) ->
string.gsub str, "\\", "\\\\"
-- proccess line
process_line = (line, config) ->
-- get times
typeset = generate_typeset line, config
-- prepare avisynth script
avs_script_path = aegisub.decode_path('?temp/script.avs')
avs_script = replace_tokens avs_trim_script, config, typeset
avs_script = avs_script\gsub "{vpath}", aegisub.project_properties().video_file
write_file(avs_script_path, avs_script)
make_folder(get_folder_from_path(typeset.typecut_name))
make_folder(get_folder_from_path(typeset.typeset_name))
execute(replace_tokens "{ffmpegExe} -i \"#{avs_script_path}\" -y -vcodec ffv1 -level 3 \"#{escape_path(typeset.typecut_name)}\"", config)
os.remove(avs_script_path)
-- trims typesets
trim_lines = (subtitles, selected_lines, active_line) ->
config = read_config()
-- check if a video file is loaded
if aegisub.project_properties().video_file == ""
log "You must load a video file!"
return
-- process lines
for i, ldx in ipairs selected_lines
process_line subtitles[ldx], config
-------------------------
-- generate typesetting script
-------------------------
generate_avs_ts_script = (subtitles, selected_lines, active_line) ->
config = read_config()
if aegisub.project_properties().video_file == ""
log "You must load a video file!"
return
script = replace_tokens avs_base_script, config
for i, ldx in ipairs selected_lines
typeset = generate_typeset subtitles[ldx], config
script = script.."\n".."TypositterMod(LWLibavVideoSource(\"#{typeset.typeset_name}\"), #{typeset.start_frame}, #{typeset.end_frame})"
avs_path = replace_tokens(config["scriptPathTemplate"], config)
write_file(avs_path, script)
log "Created AVS file: #{avs_path}"
aegisub.register_macro("After Waltz/Create Typesets", script_description, trim_lines)
aegisub.register_macro("After Waltz/Generate Script", script_description, generate_avs_ts_script)
aegisub.register_macro("After Waltz/Settings", script_description ,show_config_dialogue)
@cN3rd
Copy link
Author

cN3rd commented Jan 23, 2024

0.4.0 - drop virtualdub/lagarith workflow in favor of ffmpeg/ffv1, cleanups, use LWLibavVideoSource and TypositterMod for more updates.

Thinking about adding VapourSynth support but that requires some changes I'll have to upstream... somewhen.

@cN3rd
Copy link
Author

cN3rd commented Jan 23, 2024

0.4.1 - Fix script output

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment