I ran into a little hurdle in my
input minimization script. I was unable to create a new GMV movie file from a series of button presses within a Lua script. This prompted me to create a small tool for reading/writing button presses directly from movie files. It's a pretty general tool, so I'm giving it its own thread.
Purpose:
To parse a movie file into a table of button presses or to reverse the process, turning a sequence of button presses into a movie file entirely within Lua. The table of button presses has three indices: the first index is the frame, the second is the player, and the third is the button pressed (as used in the Lua's input.set function). This is so that the movie can be edited quickly and intuitively using input.set, with the edited movie output directly to a (possibly new) file.
Incidentally, because the format of the buttons table is essentially unchanged between the FM2 and GMV movie formats, this could allow for easy porting of a series of inputs from FM2 movie files to GMV movie files, or vice versa. I have no idea why anyone would want to do this, but hey, you can if you want.
The script:
Download RWmovies.luaLanguage: lua
local function fromFM2(filename)
local movie=io.open(filename)
if not(movie) then print("File does not exist.") return end
io.close(movie)
local buttons={}
--List of buttons in the order that they appear in the FM2 file.
local buttonlist={"right","left","down","up","start","select","B","A"}
local frame=1
for line in io.lines(filename) do
if string.find(line,"|") then
buttons[frame]={}
for player=1,2 do
buttons[frame][player]={}
local start=9*player-6
for i=1,#buttonlist do
if not(string.sub(line,start+i,start+i)==".") then
buttons[frame][player][buttonlist[i]]=true
end
end
end
frame=frame+1
end
end
return buttons
end
local function fromGMV(filename)
local movie=io.open(filename)
if not(movie) then print("File does not exist.") return end
local buttons={}
--List of buttons in the order that they appear in the GMV file.
local buttonlist={[0]="up","down","left","right","A","B","C","start"}
movie:seek("set",64)
str = movie:read("*a")
for frame=1,math.floor(string.len(str)/3) do
buttons[frame]={}
for player=1,2 do
buttons[frame][player]={}
local binbuttons=string.byte(str,3*frame-3+player,3*frame-3+player)
--This is really just NOT(binbuttons)...
binbuttons=XOR(binbuttons,255)
for i=0,#buttonlist do
if AND(binbuttons,2^i)>0 then
buttons[frame][player][buttonlist[i]]=true
end
end
end
end
return buttons
end
--The headerfilename argument is optional. If it's there, the code plucks its header and creates a new file. If it's absent, the movie MUST replace an existing file.
local function toFM2(buttons,filename,headerfilename)
local buttonlist={"right","left","down","up","start","select","B","A"}
local buttonlistFM2={"R","L","D","U","T","S","B","A"}
if headerfilename then
local from=io.open(headerfilename)
local to=io.open(filename,"w+")
local data=from:read("*a")
to:write(data)
to:flush()
io.close(from)
io.close(to)
end
local movie=io.open(filename,"r+b")
local str=movie:read("*a")
local starthere=string.find(str,"|")
movie:seek("set")
header=movie:read(starthere-1)
io.close(movie)
movie=io.open(filename,"w+")
movie:write(header)
for frame=1,#buttons do
presses="|0|"
for player=1,2 do
for i=1,#buttonlistFM2 do
if buttons[frame][player][buttonlist[i]] then
presses=presses..buttonlistFM2[i]
else
presses=presses.."."
end
end
presses=presses.."|"
end
presses=presses.."\n"
movie:write(presses)
end
io.flush(movie)
end
local function toGMV(buttons,filename,headerfilename)
local buttonlist={[0]="up","down","left","right","A","B","C","start"}
local header=""
if headerfilename then
local from=io.open(headerfilename)
header=from:read(64)
io.close(from)
else
local from=io.open(filename)
header=from:read(64)
io.close(from)
end
local to=io.open(filename,"w+")
to:write(header)
for frame=1,#buttons do
local presses=""
for player=1,2 do
local pressnum=0
for i=0,#buttonlist do
if not(buttons[frame][player][buttonlist[i]]) then
pressnum=pressnum+2^i
end
end
presses=presses..string.char(pressnum)
end
presses=presses..string.char(255)
to:write(presses)
end
io.flush(to)
end
--buttons=fromFM2("C:/someFM2file.fm2")
--buttons=fromGMV("C:\\someGMVfile.gmv")
--print(buttons)
--buttons={{{},{}},{{["A"]=true},{["B"]=true}},{{["up"]=true},{["down"]=true}},{{["left"]=true},{["right"]=true}},{{["start"]=true},{["select"]=true}},{{},{}}}
--buttons={{{},{}},{{["A"]=true},{["B"]=true}},{{["up"]=true},{["down"]=true}},{{["left"]=true},{["right"]=true}},{{["start"]=true},{["C"]=true}},{{},{}}}
--toFM2(buttons,"test.fm2","C:/SomeDirectories/someFM2file.fm2")
--toGMV(buttons,"test.txt","Test.gmv")
--Format of buttons table: buttons[frame][player][button]
How to use the script:
First, choose an FM2 movie or a GMV movie. Then, decide whether you would like to read the button presses from the movie file, write a series of button presses to the movie file, or both. I've commented out the basic features of the script at its end. You'll need to uncomment whatever you want to do.
To read an FM2 movie file:
• Uncomment the line starting "buttons=fromFM2(...".
• Replace the default file path ("C:/someFM2file.fm2") with whatever movie you'd like to open.
• Uncomment the print(buttons) line if you'd like to see the button presses in the Lua console.
To read a GMV movie file:
• Uncomment the line starting "buttons=fromGMV(...".
• Replace the default file path ("C:\\someGMVfile.gmv") with whatever movie you'd like to open.
• Uncomment the print(buttons) line if you'd like to see the button presses in the Lua console.
To write an FM2 movie file:
• Uncomment the line beginning "toFM2(...".
• Pick a source for your buttons table. This means either using the fromFM2 function or uncommenting the first line with a sample buttons table ("buttons={{{},{}..."). This is the first argument.
• Pick a destination file for your new FM2 movie. This is the second argument (default is "test.fm2"). If you do not have a third argument, this file must already exist.
• Optionally, pick a source file from which to pluck the header information. This is the third argument. The third argument is used if you want to copy, instead of replace, a movie file and you still want to preserve things like the emulator settings and rerecord count.
To write a GMV movie file:
• Uncomment the line beginning "toGMV(...".
• Pick a source for your buttons table. This means either using the fromGMV function or uncommenting the second line with a sample buttons table ("buttons={{{},{}..."). This is the first argument.
• Pick a destination file for your new GMV movie. This is the second argument (default is "test.txt"). If you do not have a third argument, this file must already exist.
• Optionally, pick a source file from which to pluck the header information. This is the third argument. The third argument is used if you want to copy, instead of replace, a movie file and you still want to preserve things like the emulator settings and rerecord count.
Things to do:
• Implement six-button controls in the GMV movie format (X, Y, Z, and mode are automatically presumed/set to false).
• Implement commands and other formats specified
here.
• Expand to include other movie formats (BKM will likely be a priority).
I probably won't be doing any of this, except by request or as I have need to, so just give the script a run, tell me how you like it, and make a request and I can probably add it in a couple of hours.
Edit: Apparently T stands for sTart, not selecT. I've switched those two buttons (but I have not re-tested the script).