Post subject: Subtitles lua script
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
I created a basic subtitles script for my E.V.O. run. That's about as much as I cared to do at the time: Have something to display texts that I want. Then zidanax wanted to use it and made minor changes to it. Seeing as there existed demand for it, I put effort into perfecting it. I actually made a slightly more advanced version involving linked lists, but it breaks horribly in FCEUX for some reason, by crashing it on script close. The version I'm posting is the array-based version. Just paste this into a file, save it as a .lua file, and run it through an emulator. If it works, you should see three different subtitles in a few seconds. You'd probably want to change them at some point, so open up the file with some sort of text editor and look for the first line of asterisks, *******. Examine that bit of text that follows to figure out what to do. [code lua]--Subtitles intended for Snes9x. Might work for other emulators. --Script mostly by FatRatKnight, zidanax added some modifications, then FatRatKnight went rampaging to make the perfect script -- Features: -- Adjustible position -- Variable duration -- Any arbitrary number of lines. -- Multiple instances of subtitles! -- Array-based version. -- Linked list seems to break horribly in FCEUX, by crashing it on close. -- This is why I'd rather hand out the array-based version. local emu if FCEU then emu= FCEU elseif snes9x then emu= snes9x elseif gens then emu= gens end local Time, Duration, x,y= {}, {}, {},{} local LineArray= {} local i= 1 -- Can take an arbitrary number of lines -- Its name is one letter long to save bytes. Lots of them. function S(st,sd , sx,sy , ...) --Time, Y-pos, duration, lines if arg.n <= 0 then return end -- Sanity Time,Duration , x,y= st,sd , sx,sy for z= 1, arg.n do if not LineArray[z] then LineArray[z]= {} end LineArray[z]= arg[z] end i=i+1 end --***************************************************************************** -- Begin text --Format: --S( Frame to appear, duration , X-pos , Y-pos , Line1, Line2, ... , LineN) -- It does not like it when things aren't in chronological order! S( 200, 800, 30, 30,"Just messing around.") S( 500, 400, 40, 39,"You can specify","an arbitrary","number of lines,","or overlapping subs!") S( 600, 500, 30,100,"This is simply a sample test.") --End text --***************************************************************************** -- Initializes important stuff. Then goes on to display said stuff. -- In co-routine form so I can create multiple instances of subtitles. function CoRoutine_DispMsg(Index, DurationOverride) if not Index then return end local D , X,Y= Duration[Index] , x[Index], y[Index] local L= {} if DurationOverride then D= DurationOverride end local n= 1 while LineArray[n] do L[n]= LineArray[n][Index] n= n+1 end n= n-1 coroutine.yield() -- Initialized. Stop here for a= 1, D do for b= 1, n do if L then gui.text( X , Y +9*b, L ) end end if coroutine.yield() == "die" then return end end end local routines= {} local LastRoutine= 0 -- Finds the correct index on state load or lua open -- Loads all text that should exist on stateload, with proper endpoints -- Will also kill previously existing subtitles function FindIndex() if LastRoutine > 0 then for j= 1, LastRoutine do coroutine.resume(routines[j],"die") routines[j]= nil end LastRoutine= 0 end i= 1 local fc= movie.framecount() while Time and Time <= fc do local endframe= Time + Duration if endframe > fc then LastRoutine= LastRoutine+1 routines[LastRoutine]= coroutine.create(CoRoutine_DispMsg) coroutine.resume( routines[LastRoutine] , i , endframe-fc ) end i= i+1 end end -- Load-state stability if savestate.registerload then savestate.registerload(FindIndex) -- Well, it exists. Use it! else -- Implement pseudo load-state detector local fc, lastfc= 0, 0 gui.register( function () fc= movie.framecount() if (fc ~= lastfc) and (fc-1 ~= lastfc) then FindIndex() end lastfc= fc end) end -- Executes co-routines. Will also clear away dead ones. function RoutineJunk() local a= 1 while a <= LastRoutine do if coroutine.status(routines[a]) == "dead" then for j= a, LastRoutine do routines[j]= routines[j+1] end LastRoutine= LastRoutine-1 else coroutine.resume(routines[a]) a= a+1 end end if FCEU then gui.drawpixel(1,1,"clear") end -- FCEUX display tends to stick end --***************************************************************************** -- Finally at main body of executable code FindIndex() while true do -- In case of state-load after the last subtitle is loaded -- Wait until the right time for the next subtitle -- Loads new subtitle routine, if any should show up, -- then runs all current routines. while Time do if Time <= movie.framecount() then LastRoutine= LastRoutine+1 routines[LastRoutine]= coroutine.create(CoRoutine_DispMsg) coroutine.resume(routines[LastRoutine],i) -- Trigger initalize code i= i+1 end RoutineJunk() emu.frameadvance() end local timer= 0 -- Bleh... Cute, random timer. -- Idle loop. Basically sit there and do nothing. while not Time do RoutineJunk() -- To get those last messages... timer= timer+1 emu.frameadvance() if timer >= 72000 then gui.text(0,200,"You have an active script...") end if timer >=216000 then gui.text(0,210,"If you want hidden messages, open the script") end end end[/code] This is simply a general-purpose subtitles script that should, short of drawing shapes and colors, cover everything you need related to subtitles.
Skilled player (1885)
Joined: 4/20/2005
Posts: 2160
Location: Norrköping, Sweden
I just tested this script, and it works well for both FCEU and FCEUX. The script looks pretty complex, but I guess there are many special cases you have to consider in order to make it work with save- and loadstating and such. Anyway, thanks for making it! :)
Player (70)
Joined: 8/24/2004
Posts: 2562
Location: Sweden
Seems good that anyone can make subtitles for their runs. What about something that also converts input files to subtitle files for files that are encoded on the site?
Editor, Skilled player (1938)
Joined: 6/15/2005
Posts: 3246
Interesting script. However, I'm puzzled as to why coroutines were used. Perhaps for speed? I'd rather use a straightforward means of displaying subtitles that only uses movie.framecount(), such as this: [code lua]local emu if FCEU then emu= FCEU elseif snes9x then emu= snes9x elseif gens then emu= gens end local fc function S(st,sd , sx,sy , ...) -- time, duration, x, y if fc >= st and fc < st+sd then for z= 1, arg.n do gui.text(sx,sy+9*(z-1),arg[z]) end end end while true do if FCEU then gui.drawpixel(1,1,"clear") end fc=movie.framecount() S( 200, 800, 30, 30,"Just messing around.") S( 500, 400, 40, 39,"You can specify","an arbitrary","number of lines,","or overlapping subs!") S( 600, 500, 30,100,"This is simply a sample test.") emu.frameadvance() end[/code]
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
FractalFusion wrote:
Interesting script. However, I'm puzzled as to why coroutines were used. Perhaps for speed?
I wanted to avoid going through several hundred (maybe thousands, if you're insane) stored subtitles every frame. I only want to deal with subtitles that should remain active. So, yes, it's for speed. That, and I wanted some practice with co-routines. This ended up being a decent enough challenge to get the concept cemented in. For reference, my E.V.O. subtitles have 178 instances of subtitles. I don't want to deal with going through all 178 only to find out I shouldn't display any of them at the time. Then do it all over again 1/60 of a second later. Other than that, the script you posted is real simple and has no obvious bugs. Even gets around the problem of dealing with subtitles that aren't in chronological order. Mine fails pretty badly in that regard. As for the size of the script, yours is plenty more compact than mine. Clearly, you have the advantage when few subtitles are concerned. When we start getting people who wants a four-digit count of subtitle instances, I'm sure a little speed would be desired...
Highness wrote:
What about something that also converts input files to subtitle files for files that are encoded on the site?
I'm not quite sure what you're asking here... Do you mean turning subtitles displayed in the emulation into a separate stream for multimedia files, like a .mkv or something? If that is what you're asking, I have no experience in that regard.
Skilled player (1306)
Joined: 9/7/2007
Posts: 1354
Location: U.S.
This looks awesome! I'll make sure to use this in my Parodius run. FractalFusion's version works fine. I'm guessing yours would work too.
Joined: 1/26/2009
Posts: 558
Location: Canada - Québec
What about creating some kind of windows programs ? That may help people to design there own subtitle lua script quickly.
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
[code lua]--Subtitles intended for Snes9x. Might work for other emulators. --Script mostly by FatRatKnight, zidanax added some modifications, then FatRatKnight went rampaging to make the perfect script -- Now with extra boxes! --Important functions: --S(StartFrame,EndFrame , Xpos,Ypos , Line1, Line2, ..., LineN) --Subtitle. --Paints the subtitle at selected location at specified time --Allows for multiple lines. --B(StartFrame,EndFrame , Xpos,Ypos , Xpos2,Ypos2 , Fill,Border) --Box. --Paints a box at selected location at specified time --Specify the color as well, will ya? --A(StartFrame,EndFrame , Xpos,Ypos , Fill,Border , Line1, Line2, ..., LineN) --Auto text+box --Paints a properly-fitting box around the subtitle you want displayed. --Manually putting in B(...) and S(...) lines for the same sub is inconvenient. --TypicalLoopOfSubbies() --By default, this is shoved into emu.registerafter() --It will handle all the subroutine junk for you. local Fn= {} local Time,End , x,y= {},{} , {},{} -- Universal local TextLines= {} -- Text local BoxW,BoxH , Fill,Border= {},{} , {},{} -- Boxes local i= 1 --***************************************************************************** local function CoRoutine_DispMsg(SelfRef, NextCo, Index) --***************************************************************************** if not Index then return end local D , X,Y= End[Index] , x[Index], y[Index] local L= {} local n= 1 while TextLines[n] do L[n]= TextLines[n][Index] -- Load lines into routine n= n+1 end n= n-1 while (movie.framecount() < D) do local cmd= coroutine.yield(SelfRef) local tst= true if NextCo then tst, cmd= coroutine.resume(NextCo,cmd) end if tst then if cmd == "die" then return "die" end NextCo= cmd else NextCo= nil -- Error took place. Just ignore it... end for j= 1, n do if L[j] then gui.text( X , Y +9*(j-1), L[j] ) end end end return NextCo end --***************************************************************************** local function CoRoutine_DispBox(SelfRef, NextCo, Index) --***************************************************************************** if not Index then return end local D , X,Y= End[Index] , x[Index], y[Index] local W,H , F,B= BoxW[Index],BoxH[Index] , Fill[Index],Border[Index] while (movie.framecount() < D) do local cmd= coroutine.yield(SelfRef) local tst= true if NextCo then tst, cmd= coroutine.resume(NextCo,cmd) end if tst then if cmd == "die" then return "die" end NextCo= cmd else NextCo= nil -- Error took place. Just ignore it... end gui.box( X,Y , W,H , F,B ) end return NextCo end -- Can take an arbitrary number of lines -- Its name is one letter long to save bytes. Lots of them. --***************************************************************************** function S(st,se , sx,sy , ...) --Time, pos, lines --***************************************************************************** if arg.n <= 0 then return end -- Sanity Fn= CoRoutine_DispMsg Time,End , x,y= st,se , sx,sy for z= 1, arg.n do if not TextLines[z] then TextLines[z]= {} end TextLines[z]= arg[z] end i=i+1 end --ox --***************************************************************************** function B(bt,be , bx,by , bw,bh , bf,bb) -- Time, pos, pos2, colors --***************************************************************************** Fn= CoRoutine_DispBox Time,End , x,y= bt,be , bx,by BoxW,BoxH , Fill,Border= bw,bh , bf,bb i=i+1 end -- [A]uto -- Boxes and subtitles, wrapped in one. -- Calculates the box size for you! --***************************************************************************** function A(at,ae , ax,ay , bf,bb , ...) --Time, pos, BoxColors, SubLines --***************************************************************************** if arg.n <= 0 then return end -- Sanity Fn= CoRoutine_DispBox Fn[i+1]= CoRoutine_DispMsg Time[i ],End[i ] , x[i ],y[i ]= at,ae , ax-2,ay-1 Time[i+1],End[i+1] , x[i+1],y[i+1]= at,ae , ax ,ay local len= 0 for z= 1, arg.n do len= math.max(len, string.len(arg[z])) if not TextLines[z] then TextLines[z]= {} end TextLines[z][i+1]= arg[z] end BoxW= ax + len*4 BoxH= ay + arg.n*9-1 Fill, Border= bf, bb i=i+2 end local routine -- Executes co-routines. --***************************************************************************** local function RoutineJunk() --***************************************************************************** local TempCo= routine if routine then local tst, cmd= coroutine.resume(routine) if not tst then routine= nil else routine= cmd end end if FCEU then -- Avert minor glitch with the display when not showing text gui.drawpixel(0,0,"clear") end end -- Finds the correct index on state load or lua open -- Loads all text that should exist on stateload, with proper endpoints -- Will also kill previously existing subtitles --***************************************************************************** function FindIndex() --***************************************************************************** if routine then coroutine.resume(routine,"die") routine= nil end i= 1 local fc= movie.framecount() while Time and Time <= fc do if End > fc then local TempCo= routine routine= coroutine.create(Fn) coroutine.resume( routine , routine , TempCo , i ) end i= i+1 end end local fc, lastfc= 0, 9999999999 -- 10 billion sounds safe; A few years --***************************************************************************** function TypicalLoopOfSubbies() --***************************************************************************** --Pseudo rewind detector fc= movie.framecount() if (fc < lastfc) then FindIndex() end lastfc= fc -- Handle adding new co-routines. while Time and (Time <= movie.framecount()) do local TempCo= routine routine= coroutine.create(Fn) coroutine.resume(routine,routine,TempCo,i) -- Trigger initalize code i= i+1 end RoutineJunk() end emu.registerafter(TypicalLoopOfSubbies)[/code] I realized I had this hanging around in my computer. It is a linked list based code that allows for painting boxes, too. It still doesn't like it when you insert lines out of chronological order based on start times, maybe I should get a sort routine in there somehow... Your actual subtitles should go in a different file. Considering I'm letting the lua interpreter handle the syntax for me, this file needs to have a require("FileNameYouUsedForTheAboveCode") as its first line, followed by function calls to B and S. Load this new file you made from the emulator and have fun. EDIT: Added a function that creates boxed text, A().
Banned User, Former player
Joined: 12/23/2004
Posts: 1850
It's tempting to try making my own subtitles script with integration to the wealth of features I have from x_functions. I'll have to give it a shot later, maybe. In any case, editing would hopefully be easier.
Perma-banned
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
Since you mentioned your x_functions script, I want to ask, have you taken a look at my own auxiliary script? I'm curious what you can add to the subtitles script. You're more familiar with your stuff than I am, and if you can produce a nice result, I will want to see it.
Skilled player (1636)
Joined: 11/15/2004
Posts: 2202
Location: Killjoy
I'm sure FRK will go crazy with this script, so I decided to create a template. I don't know about the rest of you, but adding subtitles the normal way, i.e. specifying the frame you want, the location, etc, is a giant pain in the ass. Thus, to make it easier, I decided to make a point-click script. Left click where you want the subtitle, type it in, and volia, it makes a subtitle compatible with FRK's script. This script could use a lot of functionality, a rewind function, a check subtitle function, etc. However, the core idea is there, no more 'coding' to add subtitles. Hopefully by making this easier, authors will be encouraged to add their own subs. (Note, this only works in FCEUX, due to iup). [code SubtitleAdd.lua] require("auxlib"); btn = iup.button{title="Add SubTitle"}; Sub = iup.text{size="200x",value=""}; Dur = iup.text{size="200x",value="120"}; Xpos = iup.text{size="200x",value=""}; Ypos = iup.text{size="200x",value=""}; Fr = iup.text{size="200x",value=""}; -- Add Sub function Dialg_Bx() -- set the callback function, action -- when the user clicks on the button, this function is executed -- and in this case, it fires a silly popup message btn.action = function (self) print(string.format('S( %d, %s, %d, %d, "%s")',Fr.value, Dur.value, Xpos.value, Ypos.value, Sub.value)); emu.unpause(); end dialogs = dialogs + 1; -- there is no ++ in Lua Subwin = dialogs; handles[dialogs] = iup.dialog{ title="Add Subtitle", iup.vbox{ iup.label{title="Subtitle"}, Sub, iup.label{title="Frame to Start"}, Fr, iup.label{title="Duration in Frames"}, Dur, iup.label{title="X Position"}, Xpos, iup.label{title="Y Position"}, Ypos, btn } }; end; Dialg_Bx(); --***************************************************************************** function press(button) --***************************************************************************** -- Checks if a button is pressed. -- The tables it accesses should be obvious in the next line. if keys[button] and not lastkeys[button] then return true end return false end keys, lastkeys = {},{}; lastkeys['leftclick'] = true; while (true) do -- prevent script from exiting keys = input.get(); if press('leftclick') then Fr.value = movie.framecount(); Sub.value = ""; Xpos.value = keys.xmouse; Ypos.value = keys.ymouse; handles[Subwin]:show(); emu.pause(); end; FCEU.frameadvance(); lastkeys = keys; end; [/code]
Sage advice from a friend of Jim: So put your tinfoil hat back in the closet, open your eyes to the truth, and realize that the government is in fact causing austismal cancer with it's 9/11 fluoride vaccinations of your water supply.
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
Sorry about disappearing for a while. I only now got around to taking a good look at this script. Seems pretty exclusive to FCEUX with the need for iup, but darn sweet regardless. At the moment, I'm trying to formulate something even more intuitive than the pop-up box. This would take a "run-while-paused" gui.register and something like this script's ability to type stuff on the spot. The script I linked to attempts to make FCEUX's natural subtitles easier to handle. Yep, I feel like we're competing on subtitles now. A small competition, but let's see what we can build! ... Not many emulators support a "run-while-paused" gui.register. This does limit the scope of use somewhat...
Editor, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Suggest: Make it produce an ASS compatible subtitle file (textual with timing and formatting tags) as the movie is played back.
Skilled player (1636)
Joined: 11/15/2004
Posts: 2202
Location: Killjoy
FatRatKnight wrote:
Sorry about disappearing for a while. I only now got around to taking a good look at this script. Seems pretty exclusive to FCEUX with the need for iup, but darn sweet regardless. At the moment, I'm trying to formulate something even more intuitive than the pop-up box. This would take a "run-while-paused" gui.register and something like this script's ability to type stuff on the spot. The script I linked to attempts to make FCEUX's natural subtitles easier to handle. Yep, I feel like we're competing on subtitles now. A small competition, but let's see what we can build! ... Not many emulators support a "run-while-paused" gui.register. This does limit the scope of use somewhat...
I was hoping for more of a collaboration than a competition! Also, for some run-while-paused applications, I came up with a crappy pause -
Language: lua

savestate.save(PAUSE_STATE) while paused do savestate.load(PAUSE_STATE) emu.frameadvance(); -- some code to end the pause end;
This allows you to have input on frames, while not actually moving forward. Cheesy, cheap, but it works.
Sage advice from a friend of Jim: So put your tinfoil hat back in the closet, open your eyes to the truth, and realize that the government is in fact causing austismal cancer with it's 9/11 fluoride vaccinations of your water supply.
marzojr
He/Him
Experienced player (748)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
DarkKobold: at least in Gens, something like this works: [code lua]while true do -- code that runs once per loop (and stops this loop when finished) goes here gens.redraw() gens.wait() end[/code] (this is straight from the Gens Lua docs, BTW) You could try adapting from that instead of the save/reload/advance combo you have. And yes, input.get() works as expected. And file bug reports for all emulators in which it doesn't work :-)
Marzo Junior
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
According to the documentation, that works for gens. I don't believe it works for many other emulators. I want to summarize the "run-while-paused" lua capabilities... FCEUX - gui.register DeSmuME - gui.register Final Burn Alpha - gui.register (I think. I never used this emu) PCSX - I'm not sure. gui.register works there, right? Gens - gens.wait, gens.redraw (never used this emu) Snes9x - Not possible (Oh, right. DarkKobold happened.) Visual Boy Advance - Not possible (Here too, probably) Mupen - Has no lua, if I recall correctly. Never used it. JPC-rr - I tried looking at this. I got confused... I'm pretty sure Snes9x, VBA, and DeSmuME uses the same text scheme, while FCEUX uses its own unique variable-width form. I do not have any knowledge of other emulators here in this case. Oh, don't worry. As much as it feels like a competition to me, I won't actually hide anything, and if you want my help on something, I'll be glad to give it. I'm currently thinking about clicking on one spot on the emu window as the top-left corner of the text, showing a basic display of where your next character will show up, and using the arbitrary "type whenever you want" of the sibtitler script I linked to before. And I want to be able to select any arbitrary text you want to move or whatever and hit a key to delete the whole thing entirely, put an end-frame on it, or just draw a box instead. No typing numbers on the part of the user.
Suggest: Make it produce an ASS compatible subtitle file (textual with timing and formatting tags) as the movie is played back.
Uh, hmm... That would require me knowing the format in the first place. I have no such knowledge at this time. Oh, nuts. I just realized my multitrack script can work beautifully on many more emulators than just FCEUX, with a few tweaks. And I happen to use all the ones that don't, except FCEUX. Actually, I've made a start on DeSmuME, but haven't finished it thanks to the fact I haven't figured out how to display the script's stored stylus input.
mz
Player (79)
Joined: 10/26/2007
Posts: 693
FatRatKnight wrote:
I want to summarize the "run-while-paused" lua capabilities...
FBA - gui.register PCSX - gui.register MAME - gui.register FCEU (the old one) - gui.register (I ported a very old version of your script to FBA: http://tasvideos.org/forum/viewtopic.php?p=230095#230095)
You're just fucking stupid, everyone hates you, sorry to tell you the truth. no one likes you, you're someone pretentious and TASes only to be on speed game, but don't have any hope, you won't get there.
marzojr
He/Him
Experienced player (748)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Hm. So it seems that pestering the Gens, Snex9x and VBA devs to support gui.register would be the best.most consistent course of action :-)
Marzo Junior
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
Technically, they already have the gui.register function. It's just that it's called only once per frame, and not repeatedly during pause. They might need to change how a few things work to get it to happen right. Probably still worth pestering about, considering just how useful a "run-while-paused" piece of code is. And wow, I didn't know gui.register works the way it does for so many different emulators. I originally thought the fact gui.register works the way it did for FCEUX is due to a glitch in the implementation, given what the documentation said:
Register a function to be called between a frame being prepared for displaying on your screen and it actually happening. Used when that 1 frame delay for rendering is not acceptable.
P.JBoy
Any
Editor
Joined: 3/25/2006
Posts: 850
Location: stuck in Pandora's box HELLPP!!!
I don't quite see what sort of script would be useful while the game's paused
marzojr
He/Him
Experienced player (748)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
PJBoy: Just about any sort of script where there is some sort of UI, like the point-and-click subtitle creator script DarkKobold posted above.
Marzo Junior
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
Any script which takes user input (input.get()) would almost certainly benefit from a "run-while-paused" function. Scripts that merely display information (displaying and interpreting memory addresses) and bots generally don't benefit so much from a "run-while-paused" function. Anything that doesn't involve input.get(), basically. A few things I've been doing practically requires "run-while-paused" to even work. A "subtitle maker" script will need it -- Will you want to have the game running dozens of seconds or even minutes while you're still typing out the same subtitle?
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
Here's effectively what I have, working with DeSmuME's implementation of lua: Download SubtitleMakerV1.lua
Language: 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...
Banned User, Former player
Joined: 3/10/2004
Posts: 7698
Location: Finland
It seems you found a bug in the code coloring algorithm. Edit: Seems to be fixed now.
Editor, Skilled player (1171)
Joined: 9/27/2008
Posts: 1085
I've "finished" my subtitles script, at least, as far as getting the most basic features I want in there. It's right here, in case you are too lazy to scroll two posts up to see the giant white blot. Feel free to try it out and see how well it works out. It is tweaked for use with DeSmuME, but I'm sure a little adjustment of numbers near the top would have it work well enough for other emulators that use fixed-width text. Also feel free to cannibalize any piece of code in there for your use. I'm sure someone will find use for having a way to type in any arbitrary text they'd like from the emulator window.