Here's effectively what I have, working with DeSmuME's implementation of lua:
Download SubtitleMakerV1.luaLanguage: lua
--FatRatKnight
local BIGNUM= 999999999 -- Even Desert Bus has only 216270341 frames!
local BypassFrameAdvanceKey= "N" --Set this to the frameadvance key.
local SubtitlesFile= "UnlikelyDuplicateFileName42.lua"
local WID= 6 -- Width of each character. DeSmuME uses 6.
local HGT= 9 -- Height. I'm arbitrarily using 9 here.
local identifier
-- List of commands:
local DelChar= "backspace" -- Delete a single character
local NewLine= "enter" -- New line
local LeftArr= "left"
local RightAr= "right"
local UpArrow= "up"
local DownArr= "down"
local Del_Sub= "c+X" -- Erase the whole subtitle. Hit twice to confirm
local Kil_Sta= "c+G" -- Sets the subtitle's start to frame zero
local Set_Sta= "c+H" -- Sets the subtitle's start to now
local Kil_End= "c+T" -- Sets the subtitle's end to BIGNUM
local Set_End= "c+Y" -- Sets the subtitle's end to now
local SaveTxt= "c+S" -- Saves subtitles to file
local NextSub= "numpad3" -- Selects the next active subtitle
local ResetPs= "numpad5" -- Sets subtitle's x,y to 1,1
local TglStat= "numpad6" -- Show/hide information about selected subtitile
local S_Count= "home" -- Counts current number of stored subtitles
local T_AutoS= "insert" -- Toggles whether the script saves on exit
local white= 0xFFFFFFFF
local red= 0xFF0000FF
local fade= 0xFFFFFF80
local f_red= 0xFF000080
--*****************************************************************************
function FBoxOld(x1, y1, x2, y2, color)
--*****************************************************************************
-- Gets around the problem of double-painting the corners.
-- It acts like the old-style border-only box.
if (x1 == x2) and (y1 == y2) then -- Sanity: Is it a single dot?
gui.pixel(x1,y1,color)
elseif (x1 == x2) or (y1 == y2) then -- Sanity: A straight line?
gui.line(x1,y1,x2,y2,color)
else --(x1 ~= x2) and (y1 ~= y2)
local temp
if x1 > x2 then
temp= x1; x1= x2; x2= temp -- Sanity: Without these checks,
end -- This function may end up putting
if y1 > y2 then -- two or four out-of-place pixels
temp= y1; y1= y2; y2= temp -- near the corners.
end
gui.line(x1 ,y1 ,x2-1,y1 ,color) -- top
gui.line(x2 ,y1 ,x2 ,y2-1,color) -- right
gui.line(x1+1,y2 ,x2 ,y2 ,color) -- bottom
gui.line(x1 ,y1+1,x1 ,y2 ,color) -- left
end
end
--*****************************************************************************
function FakeBox(x1, y1, x2, y2, Fill, Border)
--*****************************************************************************
-- Gets around the problem of double-painting the corners.
-- It acts like the new-style fill-and-border box. Not perfectly, however...
if not Border then Border= Fill end
gui.box(x1,y1,x2,y2,Fill,0)
FBoxOld(x1,y1,x2,y2,Border)
end
local BigNum= 0xFFFFFFFF + 1 -- 0x100000000 doesn't parse right for my needs.
--*****************************************************************************
local function ColorGlitch(color)
--*****************************************************************************
--There is a glitch in FCEUX and VBA where inserting a number with red
--represented as 0x80 or higher does not display properly. This function
--changes this number to work with this glitch.
if color >= 0x80000000 then color = color-BigNum end
return color
end
--*****************************************************************************
local function ProcessColors()
--*****************************************************************************
white= ColorGlitch(white)
red= ColorGlitch(red)
fade= ColorGlitch(fade)
f_red= ColorGlitch(f_red)
end
local box= gui.box
if stylus then --DeSmuME. Platform: DS
WID= 6; HGT= 9;
elseif snes9x then --Snes9x. Platform: SNES
WID= 4; HGT= 8; box= FakeBox
elseif vba then --VisualBoy Advance. Platforms: GBA, GB, GBC, SGB
WID= 4; HGT= 8;ProcessColors()
elseif FCEU then --FCE Ultra / FCEUX. Platform: NES.
WID= 2; HGT= 9;ProcessColors();box= FakeBox
end
local KeyTable= {
a="a",b="b",c="c",d="d",e="e",f="f",g="g",h="h",i="i",j="j",k="k",l="l",m="m",
n="n",o="o",p="p",q="q",r="r",s="s",t="t",u="u",v="v",w="w",x="x",y="y",z="z",
tilde="`",minus="-",plus="=",
leftbracket="[",rightbracket="]",backslash="\\",
semicolon=";",quote="'",
comma=",",period=".",slash="/",
A="A",B="B",C="C",D="D",E="E",F="F",G="G",H="H",I="I",J="J",K="K",L="L",M="M",
N="N",O="O",P="P",Q="Q",R="R",S="S",T="T",U="U",V="V",W="W",X="X",Y="Y",Z="Z",
TILDE="~",MINUS="_",PLUS="+",
LEFTBRACKET="{",RIGHTBRACKET="}",BACKSLASH="|",
SEMICOLON=":",QUOTE="\"",
COMMA="<",PERIOD=">",SLASH="?",
numpad1="1",numpad2="2",numpad3="3",numpad4="4",numpad5="5",
numpad6="6",numpad7="7",numpad8="8",numpad9="9",numpad0="0",
space= " ", SPACE= " "}
KeyTable["1"]="!"
KeyTable["2"]="@"
KeyTable["3"]="#"
KeyTable["4"]="$"
KeyTable["5"]="%"
KeyTable["6"]="^"
KeyTable["7"]="&"
KeyTable["8"]="*"
KeyTable["9"]="("
KeyTable["0"]=")"
local S= {}
local SubIndex= 1 -- Spot in array object is found
local Sb= {"Test",x=4,y=4,s=0,e=BIGNUM,i=1}
local ln, ch= 1, 0
S[1]= Sb
local lastkeys, keys= input.get(), input.get()
--*****************************************************************************
local function UpdateKeys() lastkeys= keys; keys= input.get() end
--*****************************************************************************
--*****************************************************************************
local function within(v,l,h) return (v>=l) and (v<=h) end
--*****************************************************************************
--*****************************************************************************
local function SortSubs()
--*****************************************************************************
table.sort(S, function (a,b) return (a.s < b.s) end)
for i= 1, #S do
S.i= i
end
end
--*****************************************************************************
local function GetRight(subtitle)
--*****************************************************************************
local x= 0
for i= 1, #subtitle do
x= math.max( x, #(subtitle[i]) )
end
return x*WID + subtitle.x + 1
end
--*****************************************************************************
local function GetBottom(subtitle)
--*****************************************************************************
return #subtitle*HGT + subtitle.y + 1
end
--*****************************************************************************
local function ClickedSubGeneral(x,y)
--*****************************************************************************
-- Horribly inefficient way: Scan EVERY sub until we get a match.
local frame= emu.framecount()
for i= 1, #S do
if ( (frame >= S[i].s)
and (frame <= S[i].e)
and (x >= S[i].x-2)
and (x <= GetRight(S[i]))
and (y >= S[i].y-2)
and (y <= GetBottom(S[i])) ) then
return i
end
end
return nil
end
--*****************************************************************************
local function ClickedSubFCEUX(x,y)
--*****************************************************************************
--Someone who made FCEUX decided to make the gui "off by 8".
--So, here's the offset to fix that!
return ClickedSubGeneral(x,y-8)
end
if FCEU then
ClickedSub= ClickedSubFCEUX
else ClickedSub= ClickedSubGeneral end
--*****************************************************************************
local function DeselectSub()
--*****************************************************************************
--... How did I forget this?
end
--*****************************************************************************
local KeyFunctions= {}
--*****************************************************************************
-- Greatly simplifies KeyReader for me.
-- Hotkeys for every funtion I want available to the user.
-------------------------------------------------------------------------------
KeyFunctions[DelChar]= function ()
-------------------------------------------------------------------------------
--Deletes a character or line break.
if ch == 0 then
if ln > 1 then
local s= table.remove(Sb,ln)
ln= ln-1
ch= #(Sb[ln])
Sb[ln]= Sb[ln] .. s
end
else
Sb[ln]= string.sub(Sb[ln],1,ch-1) .. string.sub(Sb[ln],ch+1)
ch= ch-1
end
end
-------------------------------------------------------------------------------
KeyFunctions[NewLine]= function ()
-------------------------------------------------------------------------------
--Adds a new line. Thought it did differently?
local s= string.sub(Sb[ln],ch+1)
Sb[ln]= string.sub(Sb[ln],1,ch)
ln= ln+1
ch= 0
table.insert(Sb,ln,s)
end
-------------------------------------------------------------------------------
KeyFunctions[LeftArr]= function ()
-------------------------------------------------------------------------------
--Basic left-arrow functionality. Smart enough.
ch= ch-1
if ch < 0 then
if ln > 1 then
ln= ln-1
ch= #(Sb[ln])
else
ch= 0
end
end
end
-------------------------------------------------------------------------------
KeyFunctions[RightAr]= function ()
-------------------------------------------------------------------------------
--Basic right-arrow function. Works well enough.
ch= ch+1
if ch > #(Sb[ln]) then
ln= ln+1
if ln > #Sb then
ln= #Sb
ch= #(Sb[ln])
else
ch= 0
end
end
end
-------------------------------------------------------------------------------
KeyFunctions[UpArrow]= function ()
-------------------------------------------------------------------------------
ln= math.max(ln-1,1)
ch= math.min(ch,#(Sb[ln]))
end
-------------------------------------------------------------------------------
KeyFunctions[DownArr]= function ()
-------------------------------------------------------------------------------
ln= math.min(ln+1,#Sb)
ch= math.min(ch,#(Sb[ln]))
end
-------------------------------------------------------------------------------
KeyFunctions[Del_Sub]= function ()
-------------------------------------------------------------------------------
if Sb then table.remove(S,Sb.i); Sb= nil end
end
-------------------------------------------------------------------------------
KeyFunctions[Kil_Sta]= function ()
-------------------------------------------------------------------------------
if Sb then
Sb.s= 0
print("StartPoint now zero!")
end
end
-------------------------------------------------------------------------------
KeyFunctions[Set_Sta]= function ()
-------------------------------------------------------------------------------
if Sb then
Sb.s= movie.framecount()
Sb.e= math.max(Sb.s,Sb.e)
print("Start:" , Sb.s)
end
end
-------------------------------------------------------------------------------
KeyFunctions[Kil_End]= function ()
-------------------------------------------------------------------------------
if Sb then
Sb.e= BIGNUM
print("EndPoint now super late!")
end
end
-------------------------------------------------------------------------------
KeyFunctions[Set_End]= function ()
-------------------------------------------------------------------------------
if Sb then
Sb.e= movie.framecount()
Sb.s= math.min(Sb.s,Sb.e)
print("End:" , Sb.e)
end
end
-------------------------------------------------------------------------------
KeyFunctions[SaveTxt]= function ()
-------------------------------------------------------------------------------
print("sorting...")
SortSubs()
local FileOut= io.open(SubtitlesFile,"w")
print("writing to " .. SubtitlesFile)
for i= 1, #S do
local str= string.format("S(%d,%d,%d,%d",S[i].s,S[i].e,S[i].x,S[i].y)
for j= 1, #(S[i]) do
str= str .. ",\"" .. S[i][j] .. "\""
end
str= str .. ")\n"
FileOut:write(str)
end
FileOut:close()
print("Done!")
end
-------------------------------------------------------------------------------
KeyFunctions["leftclick"]= function ()
-------------------------------------------------------------------------------
local ind= ClickedSub(keys.xmouse,keys.ymouse)
if Sb ~= S[ind] then
if Sb and (Sb[1] == "" and not Sb[2]) then
if ind and (ind > Sb.i) then ind= ind-1 end
table.remove(S,Sb.i)
end
Sb= S[ind]; if Sb then ln= #Sb; ch= #(Sb[ln]) end
end
end
-------------------------------------------------------------------------------
KeyFunctions["c+leftclick"]= function ()
-------------------------------------------------------------------------------
--Ooh, handle control+click!
if Sb and (Sb[1] == "" and not Sb[2]) then
table.remove(S,Sb.i)
end
Sb={"",x=keys.xmouse,s=movie.framecount(),y=keys.ymouse,e=BIGNUM}
table.insert(S,Sb)
Sb.i= #S; ch= 0; ln= 1
end
--*****************************************************************************
local function KeyReader()
--*****************************************************************************
UpdateKeys()
for k,v in pairs(keys) do
if not lastkeys[k] then
local ThisKey= k
if lastkeys.control then ThisKey= "c+" .. ThisKey end
if ThisKey == BypassFrameAdvanceKey then
--... It's a bypass. Don't process.
elseif KeyFunctions[ThisKey] then KeyFunctions[ThisKey]()
else
if keys.shift then ThisKey= string.upper(ThisKey)
else ThisKey= string.lower(ThisKey) end
if KeyTable[ThisKey] and Sb then
Sb[ln]= ( string.sub(Sb[ln],1,ch)
.. KeyTable[ThisKey]
.. string.sub(Sb[ln],ch+1) )
ch= ch+1
end
end
end
end
if keys.leftclick and lastkeys.leftclick and Sb then
Sb.x= Sb.x + keys.xmouse - lastkeys.xmouse
Sb.y= Sb.y + keys.ymouse - lastkeys.ymouse
end
end
--*****************************************************************************
local function ShowAllSubs()
--*****************************************************************************
for i= 1, #S do
if within(movie.framecount(), S[i].s,S[i].e) then
for j= 1, #(S[i]) do
gui.text(S[i].x, S[i].y+(j-1)*HGT, S[i][j])
end
local color= fade
if S[i] == Sb then color= white end
box(S[i].x-2, S[i].y-2, GetRight(S[i]), GetBottom(S[i]), 0, color)
elseif S[i] == Sb then
gui.opacity(.5)
for j= 1, #(S[i]) do
gui.text(S[i].x, S[i].y+(j-1)*HGT, S[i][j])
end
box(S[i].x-2, S[i].y-2, GetRight(S[i]), GetBottom(S[i]), 0, white)
gui.opacity(1)
end
end
end
--*****************************************************************************
local function DrawCursor()
--*****************************************************************************
-- Big boxy style
if Sb then
local CX= Sb.x + WID*ch
local CY= Sb.y + HGT*(ln-1)
box(CX,CY,CX+WID-1,CY+HGT-1, f_red, fade)
end
end
--*****************************************************************************
local function Fn()
--*****************************************************************************
KeyReader()
ShowAllSubs()
DrawCursor()
end
--*****************************************************************************
local function FnFCEUX()
--*****************************************************************************
KeyReader()
ShowAllSubs()
DrawCursor()
gui.pixel(0,0,0) -- FCEUX fails to clear screen with no calls to gui.
end
if FCEU then
gui.register(FnFCEUX)
else
gui.register(Fn)
end
local NoRunWhilePausedWarning= ("Warning: This emulator does not run " ..
"lua scripts while the emulation is paused. In order for the script to " ..
"react to your typing, you must have the emulator running. " ..
"Apologies for this inconvenince."
)
if FCEU then
print("FCEU emulation detected.")
print("Warning: This emulator uses variable-width text. This script is not",
"set up for variable width. The display will incorrectly estimate the",
"length of the text. Keep this in mind as you are using this script.")
elseif vba then
print("Visual Boy Advance detected.")
print(NoRunWhilePausedWarning)
elseif stylus then --DeSmuME
print("DeSmuME detected")
elseif snes9x then
print("Snes9x detected")
print(NoRunWhilePausedWarning)
else
print("Script not tailored for this emulator. Please edit this script and",
"manually change WID and HGT values to let the boxes match the text.",
"Additionally, I do not know of its nuances. Use with caution.")
end
print("\r\nThis script allows for arbitrary typing. Keep in mind that this",
"will likely react with the hotkeys set in the emulator. I have no way",
"of overriding the hotkeys. You are advised to make a copy of the",
"config file, then remove most or all hotkeys, especially those that",
"pertain to letters, numbers, or punctuation on the keyboard.")
print("\r\nThis can produce a file for use with the reader script.")
Oh, dear. It would appear the parser on this forum doesn't recognize backslash as an escape character for a following backslash.
There are numerous problems so far, but darn it! I can type stuff! And the stuff will show up!
It's a text editor at this point, and not a subtitles maker. But subtitles kind of requires a bit of text editing anyway. Without which, what are you going to do? Stare at the monitor and use your psychic powers?
This is an adaptation of the Subtitler script I've made for FCEUX's built-in subtitles. Seeing as I've only allowed for adding or removing the latest character in that script, that fact reflects here as well. In spite of having a cute cursor that moves left or right, typing a character always adds it to the end of the line. I'll, uh... Fix that, at some point...
There is support for backspace. It does a horrible job at deleting lines.
There is support for adding new lines with the enter key. It, too, does a horrible job, considering it erases the next line entirely when the cursor is on a previous line.
I've got pretty smart left and right arrow keys. No accompanying up and down arrow keys, though.
I chose not to search for special characters like carriage return or line feed, preferring to use separate strings instead. Seeing that this is the format I used in my previous subtitling stuff, I thought I should remain consistent as such. It's not all that easy, but I'm so far getting somewhere.
I'm just posting this to show progress. Not to show awesomeness, since I haven't reached that yet.
Edit: First piece of awesomeness at last: Movable text, spawnable text. Great neat stuffs! Ctrl+click to create a new box of text! Ctrl+X to kill the box!
Edit2: There we go. Now it saves subtitles in the format readable by my actual subtitle viewer script.
Edit3: It's now starting to feel more like a general purpose script. It has *known* support for FCEUX, Snes9x, VBA, and DeSmuME. I should work on the reader script to more appropriately match this script...